[RFC] checkpatch.py

Giorgos Keramidas keramida at ceid.upatras.gr
Fri Jul 3 19:54:02 CDT 2009


On Sat, 04 Jul 2009 00:59:57 +0200, Stefano Mioli <jstevie at gmail.com> wrote:
> Hi devs,
> here is a script I wrote to check a patch created by hg export against
> some of the coding style rules that can be found at
>
> 	http://mercurial.selenic.com/wiki/BasicCodingStyle
>
> It's still very simple, but Matt said he would like to have such a
> tool in contrib, so here it is.

It's a very good start indeed, thanks! :)

> It only does extra-simple checks for now, namely:
>
> - leading tabs
> - trailing whitespaces
> - DOS-style line endings
> - lines longer than 80 characters

These should probably be encoded in a dictionary or array of tuples, or
even a small class of checks, so we can write as many checks as possible
by simply adding new regexps to a vector instead of having to modify the
checking code?

    def initchecks():
        '''Compile a list of regexps and return a list of tuples with
        (regexp, compiled-regexp, description text).'''
        checkres = [ ( r'^ *\t{1,}[\s\S]*',
                       "leading indentation contains literal TAB" ),
                     ( r'[ \t]+$', "trailing whitespace" ),
                     ( r'\r\n$', "DOS-style line end" ), ]
        checks = []
        for re, desc in checkres:
            try:
                cre = re.compile(re)
                checks.append((re, cre, desc))
            except Exception, inst:
                sys.stderr.write("invalid regular expression: %s" % str(inst))
                sys.stderr.write("disabling check '%s'" % re)
        return checks

    def checkpatch(fname):
        addre = None
        try:
            addre = re.compile(r'^\+[^\+].*')
        except Exception, inst:
            sys.stderr.write("invalid regular expression: %s" % str(inst))
            return None
        try:
            f = file(fname)
        except IOError, inst:
            sys.stderr.write("%s: cannot read patch file" % fname)
            return None
        checks = initchecks()
        err = []
        warn = []
        lc = 0
        for l in lines:
            lc += 1
            l = l[1:]
            if addre and not addre.match(l):
                continue
            for (re, cre, msg) in checks:
                if cre and cre.match(l):
                    err.append("%s:%d: error: %s" % (fname, lc, msg))
            if len(l) > 80:
                    warn.append("%s:%d: line too long" % (fname, lc))

        if len(err) == 0 and len(warn) == 0:
            print "patch %s looks good" % fname
            return True
        for e in err:
            print e
        for w in warn:
            print w
        return None

> +if (__name__ == '__main__'):
> +    if (len(sys.argv) < 2):
> +        print 'usage: ./checkpatch.py patch-to-check\n'
> +        sys.exit(1)
> +
> +    filepath = str(sys.argv[1])
> +
> +    patchfile = open(filepath, 'r')
> +    lines = patchfile.readlines()
> +    patchfile.close()
> +
> +    checkpatch(lines)

Since you are handling already one file at a time in checkpatch() it may
be worth supporting more than one input file, so one can type in a
directory full of patch files:

    yourscript.py *.patch

How about something like?

    progname = os.path.basename(sys.argv[0])
    if len(sys.argv) == 1:
        print "usage: %s patchfile [patchfile ...]" % progname
        sys.exit(1)
    for fn in sys.argv[1:]:
        checkpatch(fn)


More information about the Mercurial-devel mailing list