ANN: argumentclinic.py, version 1

Fredrik Lundh effbot at telia.com
Tue Apr 4 18:27:27 EDT 2000


#! /usr/bin/env python

# Released as 'checkappend.py' to the public domain, by Tim Peters, 28
# February 2000.

# With a little luck, the eff-bot managed to turn this into something
# that looks for a few more deprecated calls...

"""argumentclinic.py -- search for deprecated multi-argument calls.

Usage:  specify one or more file or directory paths:
    argumentclinic [-v] [-m method] file_or_dir [file_or_dir] ...

Each file_or_dir is checked for commonly used but deprecated multi-
argument method calls, such as append and connect.  When a directory,
all .py files in the directory, and recursively in its subdirectories,
are checked.

Use -v for status msgs.  Use -vv for more status msgs.

Use -m to look for just one given method.  If omitted, the program
looks for append, bind, connect, connect_ex, count, has_key, and
remove.  You can use -m multiple times, to look for a bunch of methods
of your own choice.

In the absence of -v, the only output is pairs of the form

    filename(linenumber):
    line containing the suspicious method

Note that this finds multi-argument methods calls regardless of
whether they're attached to the right kind of objects.  If a module
defines a class with e.g. an connect method that takes more than one
argument, calls to that method will be listed.

Note that this will not find e.g. multi-argument list.append calls
made via a bound method object.  For example, this is not caught:

    somelist = []
    push = somelist.append
    push(1, 2, 3)
"""

__version__ = 1, 0, 0

import os
import sys
import string
import getopt
import tokenize

verbose = 0

methods = (
    "append", "count", "remove", # lists
    "has_key", # dictionaries
    "bind", "connect", "connect_ex" # sockets
    )

def errprint(*args):
    msg = string.join(args)
    sys.stderr.write(msg)
    sys.stderr.write("\n")

def main():
    args = sys.argv[1:]
    global verbose, methods
    try:
        opts, args = getopt.getopt(sys.argv[1:], "vm:")
    except getopt.error, msg:
        errprint(msg + "\n\n" + __doc__)
        return
    for opt, optarg in opts:
        if opt == '-v':
            verbose = verbose + 1
        elif opt == '-m':
            try:
                methods.append(optarg)
            except AttributeError:
                methods = [optarg]
    if not args:
        errprint(__doc__)
        return
    for arg in args:
        check(arg)

def check(file):
    if os.path.isdir(file) and not os.path.islink(file):
        if verbose:
            print "%s: listing directory" % `file`
        names = os.listdir(file)
        for name in names:
            fullname = os.path.join(file, name)
            if ((os.path.isdir(fullname) and
                 not os.path.islink(fullname))
                or os.path.normcase(name[-3:]) == ".py"):
                check(fullname)
        return

    try:
        f = open(file)
    except IOError, msg:
        errprint("%s: I/O Error: %s" % (`file`, str(msg)))
        return

    if verbose > 1:
        print "checking", `file`, "..."

    ok = ArgumentChecker(file, f).run()
    if verbose and ok:
        print "%s: Clean bill of health." % `file`

[FIND_DOT,
 FIND_METHOD,
 FIND_LPAREN,
 FIND_COMMA,
 FIND_STMT]   = range(5)

class ArgumentChecker:
    def __init__(self, fname, file):
        self.fname = fname
        self.file = file
        self.state = FIND_DOT
        self.nerrors = 0

    def run(self):
        try:
            tokenize.tokenize(self.file.readline, self.tokeneater)
        except tokenize.TokenError, msg:
            errprint("%s: Token Error: %s" % (`self.fname`, str(msg)))
            self.nerrors = self.nerrors + 1
        return self.nerrors == 0

    def tokeneater(self, type, token, start, end, line,
                NEWLINE=tokenize.NEWLINE,
                JUNK=(tokenize.COMMENT, tokenize.NL),
                OP=tokenize.OP,
                NAME=tokenize.NAME):

        state = self.state

        if type in JUNK:
            pass

        elif state is FIND_DOT:
            if type is OP and token == ".":
                state = FIND_METHOD

        elif state is FIND_METHOD:
            if type is NAME and token in methods:
                self.line = line
                self.lineno = start[0]
                state = FIND_LPAREN
            else:
                state = FIND_DOT

        elif state is FIND_LPAREN:
            if type is OP and token == "(":
                self.level = 1
                state = FIND_COMMA
            else:
                state = FIND_DOT

        elif state is FIND_COMMA:
            if type is OP:
                if token in ("(", "{", "["):
                    self.level = self.level + 1
                elif token in (")", "}", "]"):
                    self.level = self.level - 1
                    if self.level == 0:
                        state = FIND_DOT
                elif token == "," and self.level == 1:
                    self.nerrors = self.nerrors + 1
                    print "%s(%d):\n%s" % (self.fname, self.lineno,
                                           self.line)
                    # don't gripe about this stmt again
                    state = FIND_STMT

        elif state is FIND_STMT:
            if type is NEWLINE:
                state = FIND_DOT

        else:
            raise SystemError("unknown internal state '%s'" % `state`)

        self.state = state

if __name__ == '__main__':
    main()

# --- end ---





More information about the Python-list mailing list