Home | History | Annotate | Download | only in scripts
      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()
    168