diff --git a/BatchTidy.py b/BatchTidy.py new file mode 100644 index 0000000..5582e27 --- /dev/null +++ b/BatchTidy.py @@ -0,0 +1,181 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# BatchTidy.py +# Kevin Horton . 2008 Mar 27 + +"""Run pythontidy on globbed list of arguments, making backups. + +For example: + +> ./BatchTidy --suffix=\"~\" *.py + +""" + +# 2009 Oct 27 . ccr . Call PythonTidy through PythonTidyWrapper. +# . Skip copy if backup already exists. +# . Abort on error. +# . Add option to print shell script rather than run +# . tidy in real time. + +from __future__ import division +import os +import sys +from optparse import OptionParser + +ZERO = 0 +SPACE = ' ' +NULL = '' +NUL = '\x00' +NA = -1 + +PROC_PATH = os.path.split(sys.argv[ZERO])[ZERO] +PROC = os.path.join(PROC_PATH, 'PythonTidyWrapper.py') + + +def parse_options(): + """Parse the command line options. + + """ + + global OPTIONS, ARGS + usage = 'usage: %prog [options] arg' + parser = OptionParser(usage=usage) + parser.add_option( + '-d', + '--dry_run', + action='store_true', + dest='dry_run', + default=False, + help='dry_run mode. Sends output to stdout.', + ) + parser.add_option( + '-i', + '--in_place', + action='store_true', + dest='in_place', + default=False, + help='modify files in place mode. Each file is overwritten with no backup.', + ) + parser.add_option( # 2009 Oct 27 + '-r', + '--restore', + action='store_true', + dest='restore', + default=False, + help='restore mode. Replace files with their untidy backups.', + ) + parser.add_option( + '-s', + '--suffix', + dest='suffix', + default='.bak', + help='suffix for file backup. (Defaults to ".bak")', + ) + parser.add_option( # 2009 Oct 27 + '-l', + '--list_only', + action='store_true', + dest='list_only', + default=False, + help='list mode. Generate a shellscript on stdout.', + ) + parser.add_option( # 2009 Oct 27 + '-u', + '--ini_file', + dest='ini_file', + default=None, + help='PythonTidyWrapper ini_file.', + ) + (OPTIONS, ARGS) = parser.parse_args() + if len(ARGS) < 1: + parser.error('Please specify the files to tidy.') + return + + +class Process(object): # 2009 Oct 27 + + def __init__(self, command): + self.command = command + return + + def run(self): + print self.command + if OPTIONS.list_only: + pass + else: + result = os.system(self.command) + if result is ZERO: + pass + else: + print '#Error: Command failed.' + sys.exit(0x10) + return self + + +class ProcessTidy(Process): + + def __init__(self, *parms): + parms = list(parms) + if OPTIONS.ini_file is None: + pass + else: + ini_file = '"%s"' % OPTIONS.ini_file + parms.insert(ZERO, ini_file) + parms.insert(ZERO, '-u') + parms.insert(ZERO, PROC) + Process.__init__(self, SPACE.join(parms)) + return + + +class ProcessMakeExecutable(Process): + + def __init__(self, file_name): + Process.__init__(self, 'chmod +x %s' % file_name) + return + + +class ProcessBackup(Process): + + def __init__(self, file_name, bu_name): + Process.__init__( + self, + 'if test ! -e %s; then cp -a %s %s; fi' % (bu_name, file_name, bu_name), + ) + return + + +class ProcessRestore(Process): + + def __init__(self, file_name, bu_name): + Process.__init__(self, 'mv %s %s' % (bu_name, file_name)) + return + + +def tidy(): + """Run pythontidy on the specified files. + + """ + + for arg in ARGS: + arg_quote = '"%s"' % arg + if OPTIONS.dry_run: + ProcessTidy(arg_quote).run() # Results to stdout. + elif OPTIONS.in_place: + ProcessTidy(arg_quote, arg_quote).run() + ProcessMakeExecutable(arg_quote).run() + elif OPTIONS.restore: + bu_file = '"%s%s"' % (arg, OPTIONS.suffix) + ProcessRestore(arg_quote, bu_file).run() + else: + bu_file = '"%s%s"' % (arg, OPTIONS.suffix) + ProcessBackup(arg_quote, bu_file).run() + ProcessTidy(arg_quote, arg_quote).run() + ProcessMakeExecutable(arg_quote).run() + return + +if __name__ == '__main__': + parse_options() + tidy() + +# Fin! diff --git a/PythonTidy.py b/PythonTidy.py index a83a0d8..0a2a181 100644 --- a/PythonTidy.py +++ b/PythonTidy.py @@ -37,7 +37,7 @@ Alternatively, it may be invoked with file names as arguments: - python PythonTidy.py input output +o python PythonTidy.py input output Suffice it to say that *input* defaults to \'-\', the standard input, and *output* defaults to \'-\', the standard output. @@ -70,18 +70,39 @@ Search this script for "Python Version Dependency." Most of the Python 2.5 test suite passes through PythonTidy.py -unimpaired. Here are some tests that don\'t: - - test_codeccallbacks.py - test_cProfile.py - test_dis.py - test_doctest.py - test_grammar.py - test_inspect.py - test_pep263.py - test_profile.py - test_sys.py - test_trace.py +unimpaired. I ran the Python regression tests for 2.5.2 which is the +version supported by Debian 5.0 "Lenny." + +On my system these tests fail before tidying: + +o test_imageop +o test_pyclbr +o test_sys + +282 tests succeed after tidying with the default PythonTidy global +settings, but these tests fail: + +*test_grammar* exposes bug 6978 in the *compiler* module. Tuples are +immutable and hashable and thus suitable as dict indices. Whereas a +singleton tuple literal (x,) is valid as an index, the *compiler* +module parses it as x when it appears. + +*test_dis* compares "disassembled" Python byte code to what is +expected. While byte code for a tidied script should be functionally +equivalent to the untidied version, it will not necessarily be +identical. + +*test_trace* compares the line numbers in a functional trace of a +running script with those expected. A statement in a tidied script +will generally have a line number slightly different from the same +statement in the untidied version. + +*test_doctest* is an extensive suite of tests of the *doctest* module, +which itself is used to document test code within doc strings and at +need to compare instant results against those expected. One of the +tests in *test_doctest* appears to require line numbers consistent +with expectations, but tidied scripts generally violate such +conditions as explained above. The more esoteric capabilities of PythonTidy.py had to be turned off to avoid corrupting the test-suite code. In practice, you\'ll want to @@ -99,7 +120,47 @@ DEBUG = False PERSONAL = False -VERSION = '1.19' # 2009 Jun 29 +VERSION = '1.20' # 2010 Mar 10 + +# 2010 Mar 10 . v1.20 . ccr . For Kuang-che Wu: +# +# o Optionally preserve unassigned constants so that code to be tidied +# may contain blocks of commented-out lines that have been no-op'ed +# with leading and trailing triple quotes. Python scripts may declare +# constants without assigning them to a variables, but PythonTidy +# considers this wasteful and normally elides them. +# +# o Generalize an earlier exception made for PythonDoc sentinels so +# that the COMMENT_PREFIX is not inserted before any comments that +# start with doubled number-signs. +# +# o Optionally omit parentheses around tuples, which are superfluous +# after all. Normal PythonTidy behavior will be still to include them +# as a sort of tuple display analogous to list displays, dict +# displays, and yet-to-come set displays. +# +# o Kuang-che Wu has provided code that removes superfluous parens in +# complex algebraic and logical expressions, which PythonTidy used to +# interpolate to make operator precedence explicit. From now on +# PythonTidy will rely upon default operator precedence and insert +# parens only to enforce order of evaluation that is not default. +# This should make tidied code more succinct, which usually results in +# improved legibility. This fixes a PythonTidy bug noticed by +# Kuang-che Wu having to do with order of evaluation of comparisons. +# +# o As a matter of style per PEP 308, parentheses are preferred around +# conditional expressions. +# +# o Give the bitwise invert operator the same precedence as unary plus +# and unary minus. +# +# I am making other changes to PythonTidy so that a few more of the +# examples from the Python *test* module will pass: +# +# o Index literal pool by type. (Use *repr*.) +# +# o Never append a trailing comma to starred or double-starred +# arguments. # 2009 Jun 29 . v1.19 . ccr . For Daniel G. Siegel at # http://home.cs.tum.edu, *python* 2.6 tokenizer returns newlines @@ -253,9 +314,11 @@ DOUBLE_QUOTED_STRINGS = False # 2006 Dec 05 SINGLE_QUOTED_STRINGS = False # 2007 May 01 RECODE_STRINGS = False # 2006 Dec 01 -OVERRIDE_NEWLINE = None # 2006 Dec 05 +OVERRIDE_NEWLINE = '\n' # 2006 Dec 05 CAN_SPLIT_STRINGS = False # 2007 Mar 06 DOC_TAB_REPLACEMENT = '....' # 2007 May 24 +KEEP_UNASSIGNED_CONSTANTS = False # 2010 Mar 10 +PARENTHESIZE_TUPLE_DISPLAY = True # 2010 Mar 10 # Repertoire of name-transformation functions: @@ -809,7 +872,8 @@ def __init__(self, file_out): self.margin = LEFT_MARGIN self.newline = INPUT.newline # 2006 Dec 05 self.lineno = ZERO # 2006 Dec 14 - self.buffer = NULL + self.buffer = NULL + self.chunks = None # 2009 Oct 26 return def close(self): # 2006 Dec 01 @@ -1067,13 +1131,12 @@ def compensate_for_tabs(line, scol): # 2007 May 25 original = token_string.strip().decode(INPUT.coding, 'backslashreplace') decoded = eval(original) # 2007 May 01 encoded = repr(decoded) - if encoded == original: - pass - elif encoded == force_quote(original, double=False): + if (encoded == original) or (encoded == force_quote(original, double=False)): pass else: original = quote_original(token_type, original) # 2007 May 01 - original_values = self.literal_pool.setdefault(decoded, []) # 2007 May 01 + original_values = \ + self.literal_pool.setdefault(encoded, []) # 2010 Mar 10 for (tok, lineno) in original_values: # 2007 Jan 17 if tok == original: break @@ -1081,9 +1144,9 @@ def compensate_for_tabs(line, scol): # 2007 May 25 original_values.append([original, self.max_lineno]) except: pass - self.prev_lineno = NA + self.prev_lineno = -2 # 2010 Mar 10 self[self.prev_lineno] = (NA, SHEBANG) # 2007 May 25 - self[ZERO] = (NA, CODING_SPEC) # 2007 May 25 + self[NA] = (NA, CODING_SPEC) # 2007 May 25 return def merge(self, lineno=None, fin=False): @@ -1170,11 +1233,11 @@ def strip_blank_lines(text_lines): else: OUTPUT.line_init() margin_string = margin(scol) - if (margin_string == '# ') and (line == '#'): # 2008 Jan 06 - OUTPUT.line_more('##') + if (margin_string == '# ') and (line.startswith('#')): # 2010 Mar 10 + OUTPUT.line_more('#') # 2010 Mar 10 else: OUTPUT.line_more(margin(scol)) - OUTPUT.line_more(line) + OUTPUT.line_more(line) OUTPUT.line_term() if text and is_blank_line_needed() and not fin: OUTPUT.put_blank_line(3) @@ -1634,18 +1697,12 @@ def make_local_name(self): return self -class NodeOpr(Node): - - """Operator. - - """ +class NodeOpr(Node): # 2010 Mar 10 tag = 'Opr' - is_commutative = True - def put_expr(self, node, can_split=False): - if type(node) in OPERATOR_TRUMPS[type(self)] or type(node) in \ - OPERATOR_LEVEL[type(self)] and not self.is_commutative: + def put_expr(self, node, can_split=False, pos=None): + if self.is_paren_needed(node, pos): self.line_more('(', tab_set=True) node.put(can_split=True) self.line_more(')', tab_clear=True) @@ -1653,6 +1710,67 @@ def put_expr(self, node, can_split=False): node.put(can_split=can_split) return self + def is_paren_needed(self, node, pos): + return type(node) in OPERATOR_TRUMPS[type(self)] + + +class NodeOprAssoc(NodeOpr): # 2010 Mar 10 + + tag = 'A_Opr' + + +class NodeOprNotAssoc(NodeOpr): # 2010 Mar 10 + + tag = 'NA_Opr' + + def is_paren_needed(self, node, pos): + if NodeOpr.is_paren_needed(self, node, pos): + result = True + elif type(node) in OPERATOR_LEVEL[type(self)]: + result = True + else: + result = False + return result + + +class NodeOprLeftAssoc(NodeOpr): # 2010 Mar 10 + + """Left-associative operator. + + """ + + tag = 'LA_Opr' + + def is_paren_needed(self, node, pos): + if NodeOpr.is_paren_needed(self, node, pos): + result = True + elif type(node) in OPERATOR_LEVEL[type(self)]: + result = not (pos == 'left') + else: + result = False + return result + + +class NodeOprRightAssoc(NodeOpr): # 2010 Mar 10 + + """Right-associative operator. + + """ + + tag = 'RA_Opr' + + def is_paren_needed(self, node, pos): + if NodeOpr.is_paren_needed(self, node, pos): + if type(node) in [NodeUnaryAdd, NodeUnarySub]: + result = not (pos == 'right') + else: + result = True + elif type(node) in OPERATOR_LEVEL[type(self)]: + result = not (pos == 'right') + else: + result = False + return result + class NodeStr(Node): @@ -1692,7 +1810,7 @@ def set_as_str(self, str_): return self def get_as_repr(self): # 2007 May 01 - original_values = COMMENTS.literal_pool.get(self.get_as_str(), []) + original_values = COMMENTS.literal_pool.get(repr(self.get_as_str()), []) # 2010 Mar 10 if len(original_values) == 1: (result, lineno) = original_values[ZERO] else: @@ -1704,11 +1822,13 @@ def get_as_repr(self): # 2007 May 01 return result def put_doc(self, need_blank_line=ZERO): - doc = self.get_as_str() - if isinstance(doc, unicode): # 2007 May 23 - pass - else: - doc = unicode(doc, INPUT_CODING) + + def fix_newlines(text): # 2010 Mar 10 + lines = text.splitlines() + result = OUTPUT.newline.join(lines) # 2006 Dec 05 + return result + + doc = self.get_as_repr() # 2010 Mar 10 doc = doc.replace('\t', DOC_TAB_REPLACEMENT) # 2007 May 24 if LEFTJUST_DOC_STRINGS: lines = leftjust_lines(doc.strip().splitlines()) # 2007 May 25 @@ -1723,10 +1843,8 @@ def put_doc(self, need_blank_line=ZERO): lines.extend([NULL, NULL]) doc = margin.join(lines) self.line_init(need_blank_line=need_blank_line) # 2006 Dec 01 - quoted = force_quote(doc, double=True, quoted=False) # 2007 May 23 - lines = NEW_LINE_PATTERN.split(quoted) - quoted = OUTPUT.newline.join(lines) # 2006 Dec 05 - self.put_multi_line(quoted) + doc = fix_newlines(doc) # 2010 Mar 10 + self.put_multi_line(doc) self.line_term() OUTPUT.put_blank_line(5) return self @@ -1778,7 +1896,7 @@ def put(self, can_split=False): return self def get_as_repr(self): - original_values = COMMENTS.literal_pool.get(self.int, []) # 2008 Jan 6 + original_values = COMMENTS.literal_pool.get(repr(self.int), []) # 2010 Mar 10 if len(original_values) == 1: (result, lineno) = original_values[ZERO] else: @@ -1786,7 +1904,7 @@ def get_as_repr(self): return result -class NodeAdd(NodeOpr): +class NodeAdd(NodeOprAssoc): # 2010 Mar 10 """Add operation. @@ -1802,8 +1920,8 @@ def __init__(self, indent, lineno, left, right): def put(self, can_split=False): self.put_expr(self.left, can_split=can_split) - self.line_more(can_split_after=can_split, can_break_after=True) # 2007 May 23 - self.line_more(' + ') + self.line_more(SPACE, can_split_after=can_split, can_break_after=True) # 2007 May 23 + self.line_more('+ ') self.put_expr(self.right, can_split=can_split) return self @@ -1811,7 +1929,7 @@ def get_hi_lineno(self): return self.right.get_hi_lineno() -class NodeAnd(NodeOpr): +class NodeAnd(NodeOprAssoc): # 2010 Mar 10 '''Logical "and" operation. @@ -1828,8 +1946,8 @@ def put(self, can_split=False): for node in (self.nodes)[:1]: self.put_expr(node, can_split=can_split) for node in (self.nodes)[1:]: - self.line_more(can_split_after=can_split, can_break_after=True) # 2007 May 23 - self.line_more(' and ') + self.line_more(SPACE, can_split_after=can_split, can_break_after=True) # 2007 May 23 + self.line_more('and ') self.put_expr(node, can_split=can_split) return self @@ -1978,9 +2096,9 @@ def __init__(self, indent, lineno, nodes): self.nodes = [transform(indent, lineno, node) for node in nodes] return - def put(self, can_split=False): - self.line_more('(', tab_set=True) + def put(self, can_split=False, is_paren_required=True): # 2010 Mar 10 if len(self.nodes) > MAX_SEPS_SERIES: # 2007 May 24 + self.line_more('(', tab_set=True) # 2010 Mar 10 self.line_term() self.inc_margin() for node in self.nodes: @@ -1990,16 +2108,27 @@ def put(self, can_split=False): self.line_term() self.line_init() self.dec_margin() - else: + self.line_more(')', tab_clear=True) # 2010 Mar 10 + elif is_paren_required or PARENTHESIZE_TUPLE_DISPLAY: # 2010 Mar 10 + self.line_more('(', tab_set=True) # 2010 Mar 10 for node in (self.nodes)[:1]: node.put(can_split=True) - self.line_more(LIST_SEP, can_split_after=True) + self.line_more(LIST_SEP, can_split_after=True) for node in (self.nodes)[1:2]: node.put(can_split=True) for node in (self.nodes)[2:]: self.line_more(LIST_SEP, can_split_after=True) node.put(can_split=True) - self.line_more(')', tab_clear=True) + self.line_more(')', tab_clear=True) # 2010 Mar 10 + else: + for node in (self.nodes)[:1]: + node.put() + self.line_more(LIST_SEP, can_break_after=True) # 2010 Mar 10 + for node in (self.nodes)[1:2]: + node.put() + for node in (self.nodes)[2:]: + self.line_more(LIST_SEP, can_break_after=True) # 2010 Mar 10 + node.put() return self def make_local_name(self): @@ -2063,12 +2192,17 @@ def __init__(self, indent, lineno, nodes, expr): def put(self, can_split=False): self.line_init() for node in self.nodes: - node.put(can_split=can_split) + if isinstance(node, NodeAsgTuple): + node.put(can_split=can_split, is_paren_required=False) # 2010 Mar 10 + else: + node.put(can_split=can_split) self.line_more(ASSIGNMENT, can_break_after=True) if isinstance(self.expr, NodeYield): # 2006 Dec 13 self.line_more('(') self.expr.put(can_split=True) self.line_more(')') + elif isinstance(self.expr, NodeTuple): + self.expr.put(can_split=can_split, is_paren_required=False) # 2010 Mar 10 else: self.expr.put(can_split=can_split) self.line_term() @@ -2138,7 +2272,7 @@ def get_hi_lineno(self): return self.expr.get_hi_lineno() -class NodeBitAnd(NodeOpr): +class NodeBitAnd(NodeOprAssoc): # 2010 Mar 10 '''Bitwise "and" operation (set union). @@ -2155,8 +2289,8 @@ def put(self, can_split=False): for node in (self.nodes)[:1]: self.put_expr(node, can_split=can_split) for node in (self.nodes)[1:]: - self.line_more(can_split_after=can_split, can_break_after=True) # 2007 May 23 - self.line_more(' & ') + self.line_more(SPACE, can_split_after=can_split, can_break_after=True) # 2007 May 23 + self.line_more('& ') self.put_expr(node, can_split=can_split) return self @@ -2164,7 +2298,7 @@ def get_hi_lineno(self): return (self.nodes)[-1].get_hi_lineno() -class NodeBitOr(NodeOpr): +class NodeBitOr(NodeOprAssoc): # 2010 Mar 01 '''Bitwise "or" operation (set intersection). @@ -2181,8 +2315,8 @@ def put(self, can_split=False): for node in (self.nodes)[:1]: self.put_expr(node, can_split=can_split) for node in (self.nodes)[1:]: - self.line_more(can_split_after=can_split, can_break_after=True) # 2007 May 23 - self.line_more(' | ') + self.line_more(SPACE, can_split_after=can_split, can_break_after=True) # 2007 May 23 + self.line_more('| ') self.put_expr(node, can_split=can_split) return self @@ -2190,7 +2324,7 @@ def get_hi_lineno(self): return (self.nodes)[-1].get_hi_lineno() -class NodeBitXor(NodeOpr): +class NodeBitXor(NodeOprAssoc): # 2010 Mar 01 '''Bitwise "xor" operation. @@ -2207,8 +2341,8 @@ def put(self, can_split=False): for node in (self.nodes)[:1]: self.put_expr(node, can_split=can_split) for node in (self.nodes)[1:]: - self.line_more(can_split_after=can_split, can_break_after=True) # 2007 May 23 - self.line_more(' ^ ') + self.line_more(SPACE, can_split_after=can_split, can_break_after=True) # 2007 May 23 + self.line_more('^ ') self.put_expr(node, can_split=can_split) return self @@ -2279,27 +2413,33 @@ def count_seps(): if count_seps() > MAX_SEPS_FUNC_REF: # 2007 May 24 self.line_term() self.inc_margin() - for arg in self.args: - self.line_init() - arg.put(can_split=True) - self.line_more(LIST_SEP) - self.line_term() + arg_list = [(NULL, arg) for arg in self.args] # 2010 Mar 10 + has_stars = False # 2010 Mar 10 if self.star_args is None: pass else: - self.line_init() - self.line_more('*') - self.star_args.put(can_split=True) - self.line_more(LIST_SEP) - self.line_term() + arg_list.append(('*', self.star_args)) + has_stars = True if self.dstar_args is None: pass else: + arg_list.append(('**', self.dstar_args)) + has_stars = True + for (sentinel, arg) in arg_list[:-1]: # 2010 Mar 10 self.line_init() - self.line_more('**') - self.dstar_args.put(can_split=True) + self.line_more(sentinel) + arg.put(can_split=True) self.line_more(LIST_SEP) self.line_term() + for (sentinel, arg) in arg_list[-1:]: # 2010 Mar 10 + self.line_init() + self.line_more(sentinel) + arg.put(can_split=True) + if has_stars: + pass + else: + self.line_more(LIST_SEP) + self.line_term() self.line_init() self.dec_margin() else: @@ -2408,14 +2548,13 @@ def get_hi_lineno(self): return lineno -class NodeCompare(NodeOpr): +class NodeCompare(NodeOprNotAssoc): """Logical comparison. """ tag = 'Compare' - is_commutative = False def __init__(self, indent, lineno, expr, ops): Node.__init__(self, indent, lineno) @@ -2427,9 +2566,9 @@ def __init__(self, indent, lineno, expr, ops): def put(self, can_split=False): self.put_expr(self.expr, can_split=can_split) for (op, ex) in self.ops: - self.line_more(can_split_after=can_split, can_break_after=True) # 2007 May 23 - self.line_more(' %s ' % op) - ex.put(can_split=can_split) + self.line_more(SPACE, can_split_after=can_split, can_break_after=True) # 2007 May 23 + self.line_more('%s ' % op) + self.put_expr(ex, can_split=can_split) return self def get_hi_lineno(self): @@ -2466,7 +2605,7 @@ def is_str(self): # 2007 May 01 return isinstance(self.value, NodeStr) def get_as_repr(self): # 2007 May 01 - original_values = COMMENTS.literal_pool.get(self.value, []) + original_values = COMMENTS.literal_pool.get(repr(self.value), []) # 2010 Mar 10 if len(original_values) == 1: (result, lineno) = original_values[ZERO] else: @@ -2582,7 +2721,7 @@ def __init__(self, indent, lineno, expr): return def put(self, can_split=False): - if isinstance(self.expr, NodeConst): # 2007 May 01 + if isinstance(self.expr, NodeConst) and (not KEEP_UNASSIGNED_CONSTANTS): # 2010 Mar 10 pass else: self.line_init() @@ -2601,14 +2740,13 @@ def get_hi_lineno(self): return self.expr.get_hi_lineno() -class NodeDiv(NodeOpr): +class NodeDiv(NodeOprLeftAssoc): # 2010 Mar 10 """Division operation. """ tag = 'Div' - is_commutative = False def __init__(self, indent, lineno, left, right): Node.__init__(self, indent, lineno) @@ -2617,10 +2755,10 @@ def __init__(self, indent, lineno, left, right): return def put(self, can_split=False): - self.put_expr(self.left, can_split=can_split) - self.line_more(can_split_after=can_split, can_break_after=True) # 2007 May 23 - self.line_more(' / ') - self.put_expr(self.right, can_split=can_split) + self.put_expr(self.left, can_split=can_split, pos='left') # 2010 Mar 10 + self.line_more(SPACE, can_split_after=can_split, can_break_after=True) # 2007 May 23 + self.line_more('/ ') + self.put_expr(self.right, can_split=can_split, pos='right') # 2010 Mar 10 return self def get_hi_lineno(self): @@ -2704,9 +2842,15 @@ def __init__(self, indent, lineno, assign, list, body, else_): def put(self, can_split=False): self.line_init() self.line_more('for ') - self.assign.put(can_split=can_split) + if isinstance(self.assign, NodeAsgTuple): + self.assign.put(can_split=can_split, is_paren_required=False) # 2010 Mar 10 + else: + self.assign.put(can_split=can_split) self.line_more(' in ', can_break_after=True) - self.list.put(can_split=can_split) + if isinstance(self.list, NodeTuple): + self.list.put(can_split=can_split, is_paren_required=False) # 2010 Mar 10 + else: + self.list.put(can_split=can_split) self.line_more(':') self.line_term(self.body.get_lineno() - 1) self.body.put() @@ -2732,14 +2876,14 @@ def get_hi_lineno(self): return self.list.get_hi_lineno() -class NodeFloorDiv(NodeOpr): +class NodeFloorDiv(NodeOprLeftAssoc): # 2010 Mar 10 """Floor division operation. """ tag = 'FloorDiv' - is_commutative = False + def __init__(self, indent, lineno, left, right): Node.__init__(self, indent, lineno) @@ -2748,10 +2892,10 @@ def __init__(self, indent, lineno, left, right): return def put(self, can_split=False): - self.put_expr(self.left, can_split=can_split) - self.line_more(can_split_after=can_split, can_break_after=True) # 2007 May 23 - self.line_more(' // ') - self.put_expr(self.right, can_split=can_split) + self.put_expr(self.left, can_split=can_split, pos='left') # 2010 Mar 10 + self.line_more(SPACE, can_split_after=can_split, can_break_after=True) # 2007 May 23 + self.line_more('// ') + self.put_expr(self.right, can_split=can_split, pos='right') # 2010 Mar 10 return self def get_hi_lineno(self): @@ -2861,7 +3005,7 @@ def xform(self, node): return result def pair_up(self, args, defaults): - args = args[:] # This function manipulates its arguments + args = args[:] # This function manipulates its arguments defaults = defaults[:] # destructively, so make copies first. stars = [] args.reverse() @@ -3256,7 +3400,7 @@ def get_hi_lineno(self): class NodeIfExp(Node): - """Conditional assignment. + """Conditional assignment. (Ternary expression.) """ @@ -3270,11 +3414,13 @@ def __init__(self, indent, lineno, test, then, else_): return def put(self, can_split=False): - self.then.put(can_split=can_split) + self.line_more('(', tab_set=True) # 2010 Mar 10 + self.then.put(can_split=True) # 2010 Mar 10 self.line_more(' if ') - self.test.put(can_split=can_split) + self.test.put(can_split=True) # 2010 Mar 10 self.line_more(' else ') - self.else_.put() + self.else_.put(can_split=True) # 2010 Mar 10 + self.line_more(')', tab_clear=True) # 2010 Mar 10 return self def get_hi_lineno(self): @@ -3373,14 +3519,14 @@ def get_hi_lineno(self): return self.expr.get_hi_lineno() -class NodeLeftShift(NodeOpr): +class NodeLeftShift(NodeOprLeftAssoc): # 2010 Mar 01 """Bitwise shift left. """ tag = 'LeftShift' - is_commutative = False + def __init__(self, indent, lineno, left, right): Node.__init__(self, indent, lineno) @@ -3389,10 +3535,10 @@ def __init__(self, indent, lineno, left, right): return def put(self, can_split=False): - self.put_expr(self.left, can_split=can_split) - self.line_more(can_split_after=can_split, can_break_after=True) # 2007 May 23 - self.line_more(' << ') - self.put_expr(self.right, can_split=can_split) + self.put_expr(self.left, can_split=can_split, pos='left') # 2010 Mar 10 + self.line_more(SPACE, can_split_after=can_split, can_break_after=True) # 2007 May 23 + self.line_more('<< ') + self.put_expr(self.right, can_split=can_split, pos='right') # 2010 Mar 10 return self def get_hi_lineno(self): @@ -3542,14 +3688,13 @@ def get_hi_lineno(self): return self.test.get_hi_lineno() -class NodeMod(NodeOpr): +class NodeMod(NodeOprLeftAssoc): # 2010 Mar 10 """Modulus (string formatting) operation. """ tag = 'Mod' - is_commutative = False def __init__(self, indent, lineno, left, right): Node.__init__(self, indent, lineno) @@ -3558,10 +3703,10 @@ def __init__(self, indent, lineno, left, right): return def put(self, can_split=False): - self.put_expr(self.left, can_split=can_split) - self.line_more(can_split_after=can_split, can_break_after=True) # 2007 May 23 - self.line_more(' % ') - self.put_expr(self.right, can_split=can_split) + self.put_expr(self.left, can_split=can_split, pos='left') # 2010 Mar 10 + self.line_more(SPACE, can_split_after=can_split, can_break_after=True) # 2007 May 23 + self.line_more('% ') + self.put_expr(self.right, can_split=can_split, pos='right') # 2010 Mar 10 return self def get_hi_lineno(self): @@ -3615,14 +3760,13 @@ def get_lineno(self): return self.node.get_lineno() -class NodeMul(NodeOpr): +class NodeMul(NodeOprLeftAssoc): # 2010 Mar 10 """Multiply operation. """ tag = 'Mul' - is_commutative = False # ... string replication, that is. def __init__(self, indent, lineno, left, right): Node.__init__(self, indent, lineno) @@ -3631,10 +3775,10 @@ def __init__(self, indent, lineno, left, right): return def put(self, can_split=False): - self.put_expr(self.left, can_split=can_split) - self.line_more(can_split_after=can_split, can_break_after=True) # 2007 May 23 - self.line_more(' * ') - self.put_expr(self.right, can_split=can_split) + self.put_expr(self.left, can_split=can_split, pos='left') # 2010 Mar 10 + self.line_more(SPACE, can_split_after=can_split, can_break_after=True) # 2007 May 23 + self.line_more('* ') + self.put_expr(self.right, can_split=can_split, pos='right') # 2010 Mar 10 return self def get_hi_lineno(self): @@ -3691,7 +3835,7 @@ def get_hi_lineno(self): return self.expr.get_hi_lineno() -class NodeOr(NodeOpr): +class NodeOr(NodeOprAssoc): # 2010 Mar 10 '''Logical "or" operation. @@ -3708,8 +3852,8 @@ def put(self, can_split=False): for node in (self.nodes)[:1]: self.put_expr(node, can_split=can_split) for node in (self.nodes)[1:]: - self.line_more(can_split_after=can_split, can_break_after=True) # 2007 May 23 - self.line_more(' or ') + self.line_more(SPACE, can_split_after=can_split, can_break_after=True) # 2007 May 23 + self.line_more('or ') self.put_expr(node, can_split=can_split) return self @@ -3736,14 +3880,13 @@ def put(self, can_split=False): return self -class NodePower(NodeOpr): +class NodePower(NodeOprRightAssoc): # 2010 Mar 10 """Exponentiation. """ tag = 'Power' - is_commutative = False def __init__(self, indent, lineno, left, right): Node.__init__(self, indent, lineno) @@ -3752,10 +3895,10 @@ def __init__(self, indent, lineno, left, right): return def put(self, can_split=False): - self.put_expr(self.left, can_split=can_split) - self.line_more(can_split_after=can_split, can_break_after=True) # 2007 May 23 - self.line_more(' ** ') - self.put_expr(self.right, can_split=can_split) + self.put_expr(self.left, can_split=can_split, pos='left') # 2010 Mar 10 + self.line_more(SPACE, can_split_after=can_split, can_break_after=True) # 2007 May 23 + self.line_more('** ') + self.put_expr(self.right, can_split=can_split, pos='right') # 2010 Mar 10 return self def get_hi_lineno(self): @@ -3918,7 +4061,10 @@ def put(self, can_split=False): self.line_init() self.line_more('return ') if self.has_value(): - self.value.put(can_split=can_split) + if isinstance(self.value, NodeTuple): + self.value.put(can_split=can_split, is_paren_required=False) # 2010 Mar 10 + else: + self.value.put(can_split=can_split) self.line_term() return self @@ -3929,14 +4075,13 @@ def get_hi_lineno(self): return lineno -class NodeRightShift(NodeOpr): +class NodeRightShift(NodeOprLeftAssoc): # 2010 Mar 10 """Bitwise shift right. """ tag = 'RightShift' - is_commutative = False def __init__(self, indent, lineno, left, right): Node.__init__(self, indent, lineno) @@ -3945,10 +4090,10 @@ def __init__(self, indent, lineno, left, right): return def put(self, can_split=False): - self.put_expr(self.left, can_split=can_split) - self.line_more(can_split_after=can_split, can_break_after=True) # 2007 May 23 - self.line_more(' >> ') - self.put_expr(self.right, can_split=can_split) + self.put_expr(self.left, can_split=can_split, pos='left') # 2010 Mar 10 + self.line_more(SPACE, can_split_after=can_split, can_break_after=True) # 2007 May 23 + self.line_more('>> ') + self.put_expr(self.right, can_split=can_split, pos='right') # 2010 Mar 10 return self def get_hi_lineno(self): @@ -4081,14 +4226,13 @@ def marshal_names(self): return self -class NodeSub(NodeOpr): +class NodeSub(NodeOprLeftAssoc): # 2010 Mar 10 """Subtract operation. """ tag = 'Sub' - is_commutative = False def __init__(self, indent, lineno, left, right): Node.__init__(self, indent, lineno) @@ -4097,10 +4241,10 @@ def __init__(self, indent, lineno, left, right): return def put(self, can_split=False): - self.put_expr(self.left, can_split=can_split) - self.line_more(can_split_after=can_split, can_break_after=True) # 2007 May 23 - self.line_more(' - ') - self.put_expr(self.right, can_split=can_split) + self.put_expr(self.left, can_split=can_split, pos='left') # 2010 Mar 10 + self.line_more(SPACE, can_split_after=can_split, can_break_after=True) # 2007 May 23 + self.line_more('- ') + self.put_expr(self.right, can_split=can_split, pos='right') # 2010 Mar 10 return self def get_hi_lineno(self): @@ -4269,9 +4413,9 @@ def __init__(self, indent, lineno, nodes): self.nodes = [transform(indent, lineno, node) for node in nodes] return - def put(self, can_split=False): - self.line_more('(', tab_set=True) + def put(self, can_split=False, is_paren_required=True): # 2010 Mar 10 if len(self.nodes) > MAX_SEPS_SERIES: # 2007 May 24 + self.line_more('(', tab_set=True) # 2010 Mar 10 self.line_term() self.inc_margin() for node in self.nodes: @@ -4281,7 +4425,11 @@ def put(self, can_split=False): self.line_term() self.line_init() self.dec_margin() - else: + self.line_more(')', tab_clear=True) # 2010 Mar 10 + elif ((len(self.nodes) == ZERO) or + is_paren_required or + PARENTHESIZE_TUPLE_DISPLAY): # 2010 Mar 10 + self.line_more('(', tab_set=True) # 2010 Mar 10 for node in (self.nodes)[:1]: node.put(can_split=True) self.line_more(LIST_SEP, can_split_after=True) @@ -4290,7 +4438,16 @@ def put(self, can_split=False): for node in (self.nodes)[2:]: self.line_more(LIST_SEP, can_split_after=True) node.put(can_split=True) - self.line_more(')', tab_clear=True) + self.line_more(')', tab_clear=True) # 2010 Mar 10 + else: + for node in (self.nodes)[:1]: + node.put() + self.line_more(LIST_SEP, can_break_after=True) # 2010 Mar 10 + for node in (self.nodes)[1:2]: + node.put() + for node in (self.nodes)[2:]: + self.line_more(LIST_SEP, can_break_after=True) # 2010 Mar 10 + node.put() return self def get_hi_lineno(self): @@ -4475,8 +4632,7 @@ def get_hi_lineno(self): (NodeLeftShift, NodeRightShift), (NodeAdd, NodeSub), (NodeMul, NodeDiv, NodeFloorDiv, NodeMod), - (NodeUnaryAdd, NodeUnarySub), - (NodeInvert, ), + (NodeUnaryAdd, NodeUnarySub, NodeInvert, ), # 2010 Mar 10 (NodePower, ), (NodeAsgAttr, NodeGetAttr), (NodeSubscript, ), diff --git a/PythonTidyWrapper.py b/PythonTidyWrapper.py new file mode 100644 index 0000000..ca58a0f --- /dev/null +++ b/PythonTidyWrapper.py @@ -0,0 +1,445 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# PythonTidyWrapper.py +# 2007 Mar 06 . ccr + +# 2010 Sep 08 . ccr . Add JAVA_STYLE_LIST_DEDENT. +# 2010 Mar 16 . ccr . Add KEEP_UNASSIGNED_CONSTANTS and PARENTHESIZE_TUPLE_DISPLAY. +# 2007 May 25 . ccr . Changed MAX_SEPS. Add WRAP_DOC_STRINGS and DOC_TAB_REPLACEMENT. +# 2007 May 01 . ccr . Added SINGLE_QUOTED_STRINGS. + +"""Wrap PythonTidy.py with a configuration file. + +The name of the file containing the input Python script may be +supplied. If it is \"-\" or omitted, the input will be read from +standard input. + +The name of a file to contain the output Python script may be +supplied. If it is \"-\" or omitted, the output will be written to +standard output. + +""" + +from __future__ import division +import sys +import os +import optparse + +PY_VER = sys.version +if PY_VER[:3] in ['2.5', '2.6', '2.7']: # 2009 Oct 26 + import xml.etree.ElementTree + ElementTree = xml.etree.ElementTree +else: + import elementtree.ElementTree # from http://effbot.org/zone/element-index.htm + ElementTree = elementtree.ElementTree + +import PythonTidy + +ZERO = 0 +SPACE = ' ' +NULL = '' +NA = -1 + + +class XmlFile(ElementTree.ElementTree): + + """XML document. + + """ + + def __init__( + self, + file=None, + tag='global', + **extra + ): + if isinstance(file, basestring): + file = os.path.expanduser(file) + if file is None: + top_level_elt = XmlList(tag=tag, **extra) + top_level_elt.text = top_level_elt.tail = '\n' + ElementTree.ElementTree.__init__(self, + element=top_level_elt) + else: + ElementTree.ElementTree.__init__(self) + self.parse(source=file, + parser=ElementTree.XMLTreeBuilder(target=ElementTree.TreeBuilder(XmlList))) + return + + def count(self, tag=None): + return self.getroot().count(tag=tag) + + def write(self, file): + if isinstance(file, basestring): + file = os.path.expanduser(file) + return ElementTree.ElementTree.write(self, file) + + def append(self, xml_elt): + return self.getroot().append(xml_elt) + + def sort(self, tag=None, key_name='id'): + return self.getroot().sort(tag=tag, key_name=key_name) + + def index(self, tag=None, key_name='id'): + return self.getroot().index(tag=tag, key_name=key_name) + + +class XmlElt(ElementTree._ElementInterface): + + """XML element with attrib, text, and tail. + + """ + + def __init__( + self, + tag, + attrib={}, + **extra + ): + attrib = attrib.copy() + attrib.update(extra) + ElementTree._ElementInterface.__init__(self, tag=tag, + attrib=attrib) + return + + def __str__(self): + return ElementTree.tostring(self) + + +class XmlList(XmlElt): + + """Subclass an XML eltement to perform summary statistics on and + retrieve lists (or dicts) of its children. + + """ + + def count(self, tag=None): + result = ZERO + for child in self: + if tag in [None, child.tag]: + result += 1 + return result + + def sort(self, tag=None, key_name='id'): + result = [(child.attrib[key_name], child) for child in self + if tag in [None, child.tag]] + result.sort() + return result + + def index(self, tag=None, key_name='id'): + result = {} + for child in self: + if tag in [None, child.tag]: + insert(result, child.attrib[key_name], child) + return result + + +class Config(XmlFile): + + """Configuration parameters. + + """ + + def __init__( + self, + file=None, + tag='config', + **extra + ): + XmlFile.__init__(self, file=file, tag=tag, **extra) + self.root = self.getroot() + return + + def get_global(self, name): + return getattr(PythonTidy, name) + + def set_global(self, name, value): + setattr(PythonTidy, name, value) + return self + + def from_pythontidy_namespace(self): + repertoire = [ + ('COL_LIMIT', 'Width of output lines in characters.', 'int' + ), + ('INDENTATION', 'String used to indent lines.'), + ('ASSIGNMENT', + 'This is how the assignment operator is to appear.'), + ('FUNCTION_PARAM_ASSIGNMENT', + '... but this is how function-parameter assignment should appear.' + ), + ('FUNCTION_PARAM_SEP', + 'This is how function parameters are separated.'), + ('LIST_SEP', '... and this is how list items are separated.' + ), + ('SUBSCRIPT_SEP', + '... and this is how subscripts are separated.'), + ('DICT_COLON', 'This separates dictionary keys from values.' + ), + ('SLICE_COLON', + '... but this separates the start:end indices of slices.' + ), + ('COMMENT_PREFIX', + 'This is the sentinel that marks the beginning of a commentary string.' + ), + ('SHEBANG', + 'Hashbang, a line-one comment naming the Python interpreter to Unix shells.' + ), + ('CODING', 'The output character encoding (codec).'), + ('CODING_SPEC', + """Source file encoding. + +The %s in the value (if any) is replaced by the value of CODING.""", + 'replace', 'CODING'), + ('BOILERPLATE', + """Standard code block (if any). + +This is inserted after the module doc string on output."""), + ('BLANK_LINE', + 'This is how a blank line is to appear (up to the newline character).' + ), + ('KEEP_BLANK_LINES', + 'If true, preserve one blank where blank(s) are encountered.' + , 'bool'), + ('ADD_BLANK_LINES_AROUND_COMMENTS', + 'If true, set off comment blocks with blanks.', 'bool'), + ('MAX_SEPS_FUNC_DEF', + 'Split lines containing longer function definitions.', + 'int'), + ('MAX_SEPS_FUNC_REF', + 'Split lines containing longer function calls.', 'int'), + ('MAX_SEPS_SERIES', + 'Split lines containing longer lists or tuples.', 'int'), + ('MAX_SEPS_DICT', + 'Split lines containing longer dictionary definitions.', + 'int'), + ('MAX_LINES_BEFORE_SPLIT_LIT', + 'Split string literals containing more newline characters.' + , 'int'), + ('LEFT_MARGIN', 'This is how the left margin is to appear.' + ), + ('LEFTJUST_DOC_STRINGS', + 'If true, left justify doc strings.', 'bool'), + ('WRAP_DOC_STRINGS', + 'If true, wrap doc strings to COL_LIMIT.', 'bool'), + ('DOUBLE_QUOTED_STRINGS', + 'If true, use quotes instead of apostrophes for string literals.' + , 'bool'), + ('SINGLE_QUOTED_STRINGS', + 'If true, use apostrophes instead of quotes for string literals.' + , 'bool'), + ('RECODE_STRINGS', + """If true, try to decode strings. + +Attempt to use the character encoding specified in the input (if any).""", + 'bool'), + ('OVERRIDE_NEWLINE', + """This is how the newline sequence should appear. + +Normally, the first thing that looks like a newline +sequence on input is captured and used at the end of every +line of output. If this is not satisfactory, the desired +output newline sequence may be specified here."""), + ('CAN_SPLIT_STRINGS', + 'If true, longer strings are split at the COL_LIMIT.', + 'bool'), + ('DOC_TAB_REPLACEMENT', + 'This literal replaces tab characters in doc strings and comments.' + ), + ('KEEP_UNASSIGNED_CONSTANTS', + """Optionally preserve unassigned constants so that code to be tidied +may contain blocks of commented-out lines that have been no-op'ed +with leading and trailing triple quotes. Python scripts may declare +constants without assigning them to a variables, but PythonTidy +considers this wasteful and normally elides them.""", + 'bool'), + ('PARENTHESIZE_TUPLE_DISPLAY', + """Optionally omit parentheses around tuples, which are superfluous +after all. Normal PythonTidy behavior will be still to include them +as a sort of tuple display analogous to list displays, dict +displays, and yet-to-come set displays.""", + 'bool'), + ('JAVA_STYLE_LIST_DEDENT', + + """When PythonTidy splits longer lines because MAX_SEPS +are exceeded, the statement normally is closed before the margin is +restored. The closing bracket, brace, or parenthesis is placed at the +current indent level. This looks ugly to \"C\" programmers. When +JAVA_STYLE_LIST_DEDENT is True, the closing bracket, brace, or +parenthesis is brought back left to the indent level of the enclosing +statement.""", + + 'bool'), + ] + for parm in repertoire: + self.set_parm_from_namespace(*parm) + repertoire = [ + ('LOCAL_NAME_SCRIPT', + """The following are name-transformation functions used +on output to filter the local-variable names.""" + ), + ('GLOBAL_NAME_SCRIPT', + """The following are name-transformation functions used +on output to filter the global-variable names.""" + ), + ('CLASS_NAME_SCRIPT', + """The following are name-transformation functions used +on output to filter class names.""" + ), + ('FUNCTION_NAME_SCRIPT', + """The following are name-transformation functions used +on output to filter function names.""" + ), + ('FORMAL_PARAM_NAME_SCRIPT', + """The following are name-transformation functions used +on output to filter function-parameter names.""" + ), + ('ATTR_NAME_SCRIPT', + """The following are name-transformation functions used +on output to filter class-attribute names.""" + ), + ] + + for parm in repertoire: + self.set_script_from_namespace(*parm) + for parm in PythonTidy.SUBSTITUTE_FOR.iteritems(): + self.set_substitutions_from_namespace(*parm) + return self + + def set_parm_from_namespace( + self, + name, + desc, + type=None, + replacement=None, + ): + value = self.get_global(name) + if type is None: + if value is None: + value = 'None' + elif type == 'int': + value = 'int(%s)' % value + elif type == 'bool': + value = 'bool(%s)' % value + elif type == 'replace': + target = self.get_global(replacement) + value = value.replace(target, '%s') + value = 'str.replace(%s, "%%s", PythonTidy.%s)' \ + % (repr(value), replacement) + else: + raise NotImplementedError + elt = XmlList(tag='parm', name=name, value=value) + elt.tail = ''' +%s + +''' % desc.strip() + self.append(elt) + return self + + def set_script_from_namespace(self, name, desc): + group = XmlList(tag='script', name=name) + group.text = ''' +%s +''' % desc.strip() + group.tail = ''' + +''' + value = self.get_global(name) + if value is None: + pass + else: + for function in value: + elt = XmlList(tag='xform', name=function.__name__) + elt.tail = '\n' + group.append(elt) + self.append(group) + return self + + def set_substitutions_from_namespace(self, target, replacement): + elt = XmlList(tag='substitute', target=target, + replacement=replacement) + elt.tail = '\n' + self.append(elt) + return self + + def to_pythontidy_namespace(self): + for elt in self.root.findall('parm'): + self.get_parm_to_namespace(elt) + for elt in self.root.findall('script'): + self.get_script_to_namespace(elt) + substitutions = self.root.findall('substitute') + if substitutions: + PythonTidy.SUBSTITUTE_FOR = {} + for elt in substitutions: + self.get_substitutions_to_namespace(elt) + return self + + def get_parm_to_namespace(self, elt): + name = elt.attrib['name'] + value = elt.attrib['value'] + if value.startswith('int('): + value = eval(value) + elif value.startswith('bool('): + value = eval(value) + elif value.startswith('str.replace('): + value = eval(value) + elif value == 'None': + value = None + self.set_global(name, value) + return self + + def get_script_to_namespace(self, group): + name = group.attrib['name'] + result = [] + self.set_global(name, result) + for elt in group.findall('xform'): + name = elt.attrib['name'] + result.append(self.get_global(name)) + return self + + def get_substitutions_to_namespace(self, elt): + target = elt.attrib['target'] + replacement = elt.attrib['replacement'] + PythonTidy.SUBSTITUTE_FOR[target] = replacement + return self + + +PARSER = optparse.OptionParser(usage='%prog [options] [input [output]]' + , description=__doc__) +PARSER.add_option('-u', '--ini_file', + help='''Read configuration parameters from an ini_file.''' + , default=None) +PARSER.add_option('-U', '--dump', + help='''Dump default PythonTidy configuration parameters out to a file.''' + , default=None) +(OPTS, ARGS) = PARSER.parse_args() +if len(ARGS) > 2: + PARSER.error('At most, only two arguments are allowed.') +if len(ARGS) > 1: + FILE_OUTPUT = ARGS[1] +else: + FILE_OUTPUT = '-' +if FILE_OUTPUT in ['-']: + FILE_OUTPUT = sys.stdout +if len(ARGS) > ZERO: + FILE_INPUT = ARGS[ZERO] +else: + FILE_INPUT = '-' +if FILE_INPUT in ['-']: + FILE_INPUT = sys.stdin +if OPTS.dump is None: + pass +else: + CONFIG = Config() + CONFIG.from_pythontidy_namespace() + CONFIG.write(file=OPTS.dump) + sys.exit('Dump complete!') +if OPTS.ini_file is None: + pass +else: + CONFIG = Config(file=OPTS.ini_file) + CONFIG.to_pythontidy_namespace() + del CONFIG +PythonTidy.tidy_up(FILE_INPUT, FILE_OUTPUT) + +# Fin diff --git a/TestSuite.py b/TestSuite.py new file mode 100644 index 0000000..70aeed2 --- /dev/null +++ b/TestSuite.py @@ -0,0 +1,299 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# TestSuite.py +# 2009 Oct 13 . ccr + +"""Exercise PythonTidy over the Python Test Suite. + +""" + +from __future__ import division +import os +import subprocess +import re +import tarfile +import PythonTidy + +ZERO = 0 +SPACE = ' ' +NULL = '' +NUL = '\x00' +NA = -1 + +PATH_ARCHIVE = '/var/cache/apt/archives/Python-2.5.2.tar.bz2' +PATTERN_MEMBER = 'Python-2.5.2/Lib/test/' +PATH_TARGET = '/home/crhode/lab' +PATH_TESTS = os.path.join(PATH_TARGET, PATTERN_MEMBER[:-1]) +PATH_TEST_PACKAGE = os.path.split(PATH_TESTS)[ZERO] +PATH_REGRTEST_STDOUT = '/tmp/regrtest.txt' +PAT_PASS_FAIL = re.compile(r'^\d+\s+tests?\s+((?:failed)|(?:skipped)):\s+(.*)$', re.MULTILINE) +PAT_STATE = re.compile(r'^\s*(?:#\s*([^:]*):)?\s*(.*)\s*$') + +SENTINEL = { + 'run': None, + 'u_skip': 'User skipped', + 'a_skip': 'Failed after tidying', + 'b_skip': 'Failed before tidying', + 'r_fail': 'Regression test failed', + 'r_skip': 'Regression test skipped', + } + +STATE = dict( + (value, key) + for (key, value) in SENTINEL.iteritems() + ) + +class Archive(object): + + def __init__(self, path): + self.name = path + self.tar = tarfile.open(self.name) + return + + def get_members(self): + return self.tar.getmembers() + + def extract(self, pattern, path): + targets = [ + member + for member in self.get_members() + if member.name.startswith(pattern) + ] + return self.tar.extractall(path, targets) + + +class Test(object): + + def __init__(self, name, state='run'): + self.name = name + self.set_state(state) + return + + def __str__(self): + sentinel = SENTINEL[self.state] + if sentinel is None: + result = ' %s' % self.name + else: + result = '# %s: %s' % (sentinel, self.name) + return result + + def set_state(self, state): + self.state = state + return self + + def is_run(self): + return self.state in ['run'] + + +def get_test(line): + match = PAT_STATE.match(line) + (sentinel, name) = match.group(1,2) + if sentinel is None: + if line.startswith('#'): + result = Test(name=name, state='u_skip') + else: + result = Test(name=name, state='run') + else: + sentinel = sentinel.capitalize() + result = Test(name=name, state=STATE[sentinel]) + return result + + +class Repertoire(list): + + def __init__(self): + list.__init__(self) + self.file_name = os.path.join(PATH_TESTS, 'repertoire.dat') + return + + def get_from_dir(self): + for test in os.listdir(PATH_TESTS): + if test.startswith('test_') and test.endswith('.py'): + test = Test(test[:-3]) + self.append(test) + return self + + def load(self): + del self[:] + flob = open(self.file_name, 'r') + for line in flob: + test = get_test(line[:-1]) + self.append(test) + flob.close() + return self + + def save(self): + flob = open(self.file_name, 'w') + for test in self: + flob.write('%s\n' % str(test)) + flob.close() + return self + + def spike(self): + + """On my system these tests fail before tidying: + + """ + + for test in self: + if test.name in [ + 'test_imageop', + 'test_pyclbr', + 'test_sys', + ]: + test.set_state('b_skip') + return self + + def omit(self): + + """These tests fail after tidying: + + """ + + for test in self: + if test.name in [ + 'test_grammar', + + # *test_grammar* exposes bug 6978 in + # the *compiler* module. Tuples are + # immutable and hashable and thus + # suitable as dict indices. Whereas a + # singleton tuple literal (x,) is + # valid as an index, the *compiler* + # module parses it as x when it + # appears. + + 'test_dis', + + # *test_dis* compares "disassembled" + # Python byte code to what is + # expected. While byte code for a + # tidied script should be functionally + # equivalent to the untidied version, + # it will not necessarily be + # identical. + + 'test_trace', + + # *test_trace* compares the line + # numbers in a functional trace of a + # running script with those expected. + # A statement in a tidied script will + # generally have a line number + # slightly different from the same + # statement in the untidied version. + + 'test_doctest', + + # *test_doctest* is an extensive suite + # of tests of the *doctest* module, + # which itself is used to document + # test code within docstrings and at + # need to compare instant results + # against those expected. One of the + # tests in *test_doctest* appears to + # require line numbers consistent with + # expectations, but tidied scripts + # generally violate such conditions as + # explained above. + + ]: + test.set_state('a_skip') + return self + + def log_results(self): + flob = open(PATH_REGRTEST_STDOUT, 'r') + _buffer = flob.read() + flob.close() + _buffer = _buffer.replace('\n ', SPACE) + result = PAT_PASS_FAIL.split(_buffer) + while result: + sentinel = result.pop(ZERO) + if sentinel in ['failed', 'skipped']: + if result: + tests = result.pop(ZERO) + tests = tests.split() + for test in tests: + self.log_result(sentinel, test) + return self + + def log_result(self, sentinel, target): + for test in self: + if test.name == target: + if sentinel in ['failed']: + test.set_state('r_fail') + elif sentinel in ['skipped']: + test.set_state('r_skip') + break + return self + + def tidy_all(self): + for test in self: + if test.is_run(): + self.tidy(test.name) + return self + + def tidy(self, name): + test_name = os.path.join(PATH_TESTS, '%s.py' % name) + back_name = os.path.join(PATH_TESTS, '%s.bak' % name) + if os.path.exists(back_name): + pass + else: + os.rename(test_name, back_name) + print 'Converting', name + PythonTidy.tidy_up(file_in=back_name, file_out=test_name) + return self + + def untidy_all(self): + for test in self: + self.untidy(test.name) + return self + + def untidy(self, name): + test_name = os.path.join(PATH_TESTS, '%s.py' % name) + back_name = os.path.join(PATH_TESTS, '%s.bak' % name) + if os.path.exists(back_name): + os.rename(back_name, test_name) + return self + + +class Run(subprocess.Popen): + + def __init__(self, repertoire): + results = open(PATH_REGRTEST_STDOUT, 'w') + env = os.environ + env.setdefault('PYTHONPATH', PATH_TEST_PACKAGE) + subprocess.Popen.__init__( + self, + '%s/regrtest.py -f %s' % (PATH_TESTS, repertoire.file_name), + stdout=results, + env=env, + shell=True, + ) + results.close() + return + + +def main_line(): + Archive(PATH_ARCHIVE).extract(PATTERN_MEMBER, PATH_TARGET) + repertoire = Repertoire() + repertoire.get_from_dir() + repertoire.spike() + repertoire.save() + repertoire.load() + repertoire.omit() + repertoire.untidy_all() + repertoire.tidy_all() + process = Run(repertoire) + retcd = process.wait() + repertoire.log_results() + repertoire.save() + return + + +if __name__ == "__main__": + main_line() + + +# Fin diff --git a/config.xml b/config.xml new file mode 100644 index 0000000..3538996 --- /dev/null +++ b/config.xml @@ -0,0 +1,346 @@ + + +Width of output lines in characters. + + +String used to indent lines. + + +This is how the assignment operator is to appear. + + +... but this is how function-parameter assignment should appear. + + +This is how function parameters are separated. + + +... and this is how list items are separated. + + +... and this is how subscripts are separated. + + +This separates dictionary keys from values. + + +... but this separates the start:end indices of slices. + + +This is the sentinel that marks the beginning of a commentary string. + + +Hashbang, a line-one comment naming the Python interpreter to Unix shells. + + +The output character encoding (codec). + + +Source file encoding. + +The %s in the value (if any) is replaced by the value of CODING. + + +Standard code block (if any). + +This is inserted after the module doc string on output. + + +This is how a blank line is to appear (up to the newline character). + + +If true, preserve one blank where blank(s) are encountered. + + +If true, set off comment blocks with blanks. + + +Split lines containing longer function definitions. + + +Split lines containing longer function calls. + + +Split lines containing longer lists or tuples. + + +Split lines containing longer dictionary definitions. + + +Split string literals containing more newline characters. + + +This is how the left margin is to appear. + + +If true, left justify doc strings. + + +If true, wrap doc strings to COL_LIMIT. + + +If true, use quotes instead of apostrophes for string literals. + + +If true, use apostrophes instead of quotes for string literals. + + +If true, try to decode strings. + +Attempt to use the character encoding specified in the input (if any). + + +This is how the newline sequence should appear. + +Normally, the first thing that looks like a newline +sequence on input is captured and used at the end of every +line of output. If this is not satisfactory, the desired +output newline sequence may be specified here. + + +If true, longer strings are split at the COL_LIMIT. + + +This literal replaces tab characters in doc strings and comments. + + +Optionally preserve unassigned constants so that code to be tidied +may contain blocks of commented-out lines that have been no-op'ed +with leading and trailing triple quotes. Python scripts may declare +constants without assigning them to a variables, but PythonTidy +considers this wasteful and normally elides them. + + +Optionally omit parentheses around tuples, which are superfluous +after all. Normal PythonTidy behavior will be still to include them +as a sort of tuple display analogous to list displays, dict +displays, and yet-to-come set displays. + + +When PythonTidy splits longer lines because MAX_SEPS +are exceeded, the statement normally is closed before the margin is +restored. The closing bracket, brace, or parenthesis is placed at the +current indent level. This looks ugly to "C" programmers. When +JAVA_STYLE_LIST_DEDENT is True, the closing bracket, brace, or +parenthesis is brought back left to the indent level of the enclosing +statement. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +