1#! /usr/bin/env python 2 3# Released to the public domain, by Tim Peters, 28 February 2000. 4 5"""checkappend.py -- search for multi-argument .append() calls. 6 7Usage: specify one or more file or directory paths: 8 checkappend [-v] file_or_dir [file_or_dir] ... 9 10Each file_or_dir is checked for multi-argument .append() calls. When 11a directory, all .py files in the directory, and recursively in its 12subdirectories, are checked. 13 14Use -v for status msgs. Use -vv for more status msgs. 15 16In the absence of -v, the only output is pairs of the form 17 18 filename(linenumber): 19 line containing the suspicious append 20 21Note that this finds multi-argument append calls regardless of whether 22they're attached to list objects. If a module defines a class with an 23append method that takes more than one argument, calls to that method 24will be listed. 25 26Note that this will not find multi-argument list.append calls made via a 27bound method object. For example, this is not caught: 28 29 somelist = [] 30 push = somelist.append 31 push(1, 2, 3) 32""" 33 34__version__ = 1, 0, 0 35 36import os 37import sys 38import getopt 39import tokenize 40 41verbose = 0 42 43def errprint(*args): 44 msg = ' '.join(args) 45 sys.stderr.write(msg) 46 sys.stderr.write("\n") 47 48def main(): 49 args = sys.argv[1:] 50 global verbose 51 try: 52 opts, args = getopt.getopt(sys.argv[1:], "v") 53 except getopt.error, msg: 54 errprint(str(msg) + "\n\n" + __doc__) 55 return 56 for opt, optarg in opts: 57 if opt == '-v': 58 verbose = verbose + 1 59 if not args: 60 errprint(__doc__) 61 return 62 for arg in args: 63 check(arg) 64 65def check(file): 66 if os.path.isdir(file) and not os.path.islink(file): 67 if verbose: 68 print "%r: listing directory" % (file,) 69 names = os.listdir(file) 70 for name in names: 71 fullname = os.path.join(file, name) 72 if ((os.path.isdir(fullname) and 73 not os.path.islink(fullname)) 74 or os.path.normcase(name[-3:]) == ".py"): 75 check(fullname) 76 return 77 78 try: 79 f = open(file) 80 except IOError, msg: 81 errprint("%r: I/O Error: %s" % (file, msg)) 82 return 83 84 if verbose > 1: 85 print "checking %r ..." % (file,) 86 87 ok = AppendChecker(file, f).run() 88 if verbose and ok: 89 print "%r: Clean bill of health." % (file,) 90 91[FIND_DOT, 92 FIND_APPEND, 93 FIND_LPAREN, 94 FIND_COMMA, 95 FIND_STMT] = range(5) 96 97class AppendChecker: 98 def __init__(self, fname, file): 99 self.fname = fname 100 self.file = file 101 self.state = FIND_DOT 102 self.nerrors = 0 103 104 def run(self): 105 try: 106 tokenize.tokenize(self.file.readline, self.tokeneater) 107 except tokenize.TokenError, msg: 108 errprint("%r: Token Error: %s" % (self.fname, msg)) 109 self.nerrors = self.nerrors + 1 110 return self.nerrors == 0 111 112 def tokeneater(self, type, token, start, end, line, 113 NEWLINE=tokenize.NEWLINE, 114 JUNK=(tokenize.COMMENT, tokenize.NL), 115 OP=tokenize.OP, 116 NAME=tokenize.NAME): 117 118 state = self.state 119 120 if type in JUNK: 121 pass 122 123 elif state is FIND_DOT: 124 if type is OP and token == ".": 125 state = FIND_APPEND 126 127 elif state is FIND_APPEND: 128 if type is NAME and token == "append": 129 self.line = line 130 self.lineno = start[0] 131 state = FIND_LPAREN 132 else: 133 state = FIND_DOT 134 135 elif state is FIND_LPAREN: 136 if type is OP and token == "(": 137 self.level = 1 138 state = FIND_COMMA 139 else: 140 state = FIND_DOT 141 142 elif state is FIND_COMMA: 143 if type is OP: 144 if token in ("(", "{", "["): 145 self.level = self.level + 1 146 elif token in (")", "}", "]"): 147 self.level = self.level - 1 148 if self.level == 0: 149 state = FIND_DOT 150 elif token == "," and self.level == 1: 151 self.nerrors = self.nerrors + 1 152 print "%s(%d):\n%s" % (self.fname, self.lineno, 153 self.line) 154 # don't gripe about this stmt again 155 state = FIND_STMT 156 157 elif state is FIND_STMT: 158 if type is NEWLINE: 159 state = FIND_DOT 160 161 else: 162 raise SystemError("unknown internal state '%r'" % (state,)) 163 164 self.state = state 165 166if __name__ == '__main__': 167 main() 168