|
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 |
|
7 Usage: specify one or more file or directory paths: |
|
8 checkappend [-v] file_or_dir [file_or_dir] ... |
|
9 |
|
10 Each file_or_dir is checked for multi-argument .append() calls. When |
|
11 a directory, all .py files in the directory, and recursively in its |
|
12 subdirectories, are checked. |
|
13 |
|
14 Use -v for status msgs. Use -vv for more status msgs. |
|
15 |
|
16 In the absence of -v, the only output is pairs of the form |
|
17 |
|
18 filename(linenumber): |
|
19 line containing the suspicious append |
|
20 |
|
21 Note that this finds multi-argument append calls regardless of whether |
|
22 they're attached to list objects. If a module defines a class with an |
|
23 append method that takes more than one argument, calls to that method |
|
24 will be listed. |
|
25 |
|
26 Note that this will not find multi-argument list.append calls made via a |
|
27 bound 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 |
|
36 import os |
|
37 import sys |
|
38 import getopt |
|
39 import tokenize |
|
40 |
|
41 verbose = 0 |
|
42 |
|
43 def errprint(*args): |
|
44 msg = ' '.join(args) |
|
45 sys.stderr.write(msg) |
|
46 sys.stderr.write("\n") |
|
47 |
|
48 def 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 |
|
65 def 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 |
|
97 class 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 |
|
166 if __name__ == '__main__': |
|
167 main() |