style.py revision 4794:88afe390fc0f
1360SN/A#! /usr/bin/env python
210850SGiacomo.Gabrielli@arm.com# Copyright (c) 2007 The Regents of The University of Michigan
310796Sbrandon.potter@amd.com# All rights reserved.
410027SChris.Adeniyi-Jones@arm.com#
510027SChris.Adeniyi-Jones@arm.com# Redistribution and use in source and binary forms, with or without
610027SChris.Adeniyi-Jones@arm.com# modification, are permitted provided that the following conditions are
710027SChris.Adeniyi-Jones@arm.com# met: redistributions of source code must retain the above copyright
810027SChris.Adeniyi-Jones@arm.com# notice, this list of conditions and the following disclaimer;
910027SChris.Adeniyi-Jones@arm.com# redistributions in binary form must reproduce the above copyright
1010027SChris.Adeniyi-Jones@arm.com# notice, this list of conditions and the following disclaimer in the
1110027SChris.Adeniyi-Jones@arm.com# documentation and/or other materials provided with the distribution;
1210027SChris.Adeniyi-Jones@arm.com# neither the name of the copyright holders nor the names of its
1310027SChris.Adeniyi-Jones@arm.com# contributors may be used to endorse or promote products derived from
1410027SChris.Adeniyi-Jones@arm.com# this software without specific prior written permission.
151458SN/A#
16360SN/A# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17360SN/A# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18360SN/A# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19360SN/A# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20360SN/A# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21360SN/A# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22360SN/A# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23360SN/A# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24360SN/A# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25360SN/A# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26360SN/A# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27360SN/A#
28360SN/A# Authors: Nathan Binkert
29360SN/A
30360SN/Aimport re
31360SN/Aimport os
32360SN/Aimport sys
33360SN/A
34360SN/Alead = re.compile(r'^([ \t]+)')
35360SN/Atrail = re.compile(r'([ \t]+)$')
36360SN/Aany_control = re.compile(r'\b(if|while|for)[ \t]*[(]')
37360SN/Agood_control = re.compile(r'\b(if|while|for) [(]')
38360SN/A
39360SN/Alang_types = { 'c'   : "C",
402665Ssaidi@eecs.umich.edu               'h'   : "C",
412665Ssaidi@eecs.umich.edu               'cc'  : "C++",
422665Ssaidi@eecs.umich.edu               'hh'  : "C++",
43360SN/A               'cxx' : "C++",
44360SN/A               'hxx' : "C++",
451354SN/A               'cpp' : "C++",
461354SN/A               'hpp' : "C++",
47360SN/A               'C'   : "C++",
482764Sstever@eecs.umich.edu               'H'   : "C++",
499202Spalle@lyckegaard.dk               'i'   : "swig",
509202Spalle@lyckegaard.dk               'py'  : "python",
512064SN/A               's'   : "asm",
52360SN/A               'S'   : "asm",
53360SN/A               'isa' : "isa" }
54360SN/Adef file_type(filename):
55360SN/A    extension = filename.split('.')
56360SN/A    extension = len(extension) > 1 and extension[-1]
57360SN/A    return lang_types.get(extension, None)
581809SN/A
595543Ssaidi@eecs.umich.eduwhitespace_types = ('C', 'C++', 'swig', 'python', 'asm', 'isa')
601809SN/Adef whitespace_file(filename):
613113Sgblack@eecs.umich.edu    if file_type(filename) in whitespace_types:
628229Snate@binkert.org        return True
638229Snate@binkert.org
643113Sgblack@eecs.umich.edu    if filename.startswith("SCons"):
657075Snate@binkert.org        return True
668229Snate@binkert.org
677075Snate@binkert.org    return False
68360SN/A
692474SN/Aformat_types = ( 'C', 'C++' )
705543Ssaidi@eecs.umich.edudef format_file(filename):
712462SN/A    if file_type(filename) in format_types:
721354SN/A        return True
736216Snate@binkert.org
746658Snate@binkert.org    return False
752474SN/A
762680Sktlim@umich.edudef checkwhite_line(line):
778232Snate@binkert.org    match = lead.search(line)
788229Snate@binkert.org    if match and match.group(1).find('\t') != -1:
797678Sgblack@eecs.umich.edu        return False
8010496Ssteve.reinhardt@amd.com
818229Snate@binkert.org    match = trail.search(line)
8210497Ssteve.reinhardt@amd.com    if match:
838766Sgblack@eecs.umich.edu        return False
846640Svince@csl.cornell.edu
85360SN/A    return True
86360SN/A
87360SN/Adef checkwhite(filename):
88360SN/A    if not whitespace_file(filename):
89360SN/A        return
90360SN/A
91360SN/A    try:
92360SN/A        f = file(filename, 'r+')
93378SN/A    except OSError, msg:
941450SN/A        print 'could not open file %s: %s' % (filename, msg)
953114Sgblack@eecs.umich.edu        return
96360SN/A
975543Ssaidi@eecs.umich.edu    for num,line in enumerate(f):
985543Ssaidi@eecs.umich.edu        if not checkwhite_line(line):
995543Ssaidi@eecs.umich.edu            yield line,num + 1
10010831Ssteve.reinhardt@amd.com
101360SN/Adef fixwhite_line(line, tabsize):
102360SN/A    if lead.search(line):
103360SN/A        newline = ''
104360SN/A        for i,c in enumerate(line):
105360SN/A            if c == ' ':
1062680Sktlim@umich.edu                newline += ' '
107360SN/A            elif c == '\t':
10810831Ssteve.reinhardt@amd.com                newline += ' ' * (tabsize - len(newline) % tabsize)
10910831Ssteve.reinhardt@amd.com            else:
110360SN/A                newline += line[i:]
111360SN/A                break
112360SN/A
113360SN/A        line = newline
11410831Ssteve.reinhardt@amd.com
115360SN/A    return line.rstrip() + '\n'
116360SN/A
117360SN/Adef fixwhite(filename, tabsize, fixonly=None):
118360SN/A    if not whitespace_file(filename):
1193114Sgblack@eecs.umich.edu        return
12010831Ssteve.reinhardt@amd.com
12110831Ssteve.reinhardt@amd.com    try:
12210831Ssteve.reinhardt@amd.com        f = file(filename, 'r+')
123360SN/A    except OSError, msg:
124360SN/A        print 'could not open file %s: %s' % (filename, msg)
125360SN/A        return
126360SN/A
127360SN/A    lines = list(f)
128360SN/A
129360SN/A    f.seek(0)
130360SN/A    f.truncate()
131360SN/A
132360SN/A    for i,line in enumerate(lines):
133360SN/A        if fixonly is None or i in fixonly:
134360SN/A            line = fixwhite_line(line, tabsize)
135378SN/A
1361706SN/A        print >>f, line,
1373114Sgblack@eecs.umich.edu
138378SN/Adef linelen(line):
139378SN/A    tabs = line.count('\t')
140378SN/A    if not tabs:
141378SN/A        return len(line)
142378SN/A
1431706SN/A    count = 0
1443114Sgblack@eecs.umich.edu    for c in line:
145360SN/A        if c == '\t':
1466109Ssanchezd@stanford.edu            count += tabsize - count % tabsize
1471706SN/A        else:
1483114Sgblack@eecs.umich.edu            count += 1
149378SN/A
1506109Ssanchezd@stanford.edu    return count
1516109Ssanchezd@stanford.edu
1526109Ssanchezd@stanford.educlass ValidationStats(object):
1536109Ssanchezd@stanford.edu    def __init__(self):
154378SN/A        self.toolong = 0
1551706SN/A        self.toolong80 = 0
1563114Sgblack@eecs.umich.edu        self.leadtabs = 0
157378SN/A        self.trailwhite = 0
1585748SSteve.Reinhardt@amd.com        self.badcontrol = 0
1595748SSteve.Reinhardt@amd.com        self.cret = 0
1605748SSteve.Reinhardt@amd.com
161378SN/A    def dump(self):
162378SN/A        print '''\
1631706SN/A%d violations of lines over 79 chars. %d of which are 80 chars exactly.
1643114Sgblack@eecs.umich.edu%d cases of whitespace at the end of a line.
165378SN/A%d cases of tabs to indent.
166378SN/A%d bad parens after if/while/for.
1671706SN/A%d carriage returns found.
1683114Sgblack@eecs.umich.edu''' % (self.toolong, self.toolong80, self.trailwhite, self.leadtabs,
169378SN/A       self.badcontrol, self.cret)
170378SN/A
1711706SN/A    def __nonzero__(self):
1723114Sgblack@eecs.umich.edu        return self.toolong or self.toolong80 or self.leadtabs or \
173378SN/A               self.trailwhite or self.badcontrol or self.cret
174378SN/A
1751706SN/Adef validate(filename, stats, verbose, exit_code):
1763114Sgblack@eecs.umich.edu    if not format_file(filename):
177378SN/A        return
1784118Sgblack@eecs.umich.edu
1794118Sgblack@eecs.umich.edu    def msg(lineno, line, message):
1804118Sgblack@eecs.umich.edu        print '%s:%d>' % (filename, lineno + 1), message
1814118Sgblack@eecs.umich.edu        if verbose > 2:
182378SN/A            print line
1831706SN/A
1843114Sgblack@eecs.umich.edu    def bad():
185378SN/A        if exit_code is not None:
186378SN/A            sys.exit(exit_code)
1871706SN/A
1883114Sgblack@eecs.umich.edu    cpp = filename.endswith('.cc') or filename.endswith('.hh')
189360SN/A    py = filename.endswith('.py')
1905513SMichael.Adler@intel.com
1915513SMichael.Adler@intel.com    if py + cpp != 1:
1925513SMichael.Adler@intel.com        raise AttributeError, \
1935513SMichael.Adler@intel.com              "I don't know how to deal with the file %s" % filename
19410203SAli.Saidi@ARM.com
19510203SAli.Saidi@ARM.com    try:
19610203SAli.Saidi@ARM.com        f = file(filename, 'r')
19710203SAli.Saidi@ARM.com    except OSError:
1985513SMichael.Adler@intel.com        if verbose > 0:
1995513SMichael.Adler@intel.com            print 'could not open file %s' % filename
2005513SMichael.Adler@intel.com        bad()
201511SN/A        return
20210633Smichaelupton@gmail.com
20310633Smichaelupton@gmail.com    for i,line in enumerate(f):
20410633Smichaelupton@gmail.com        line = line.rstrip('\n')
2051706SN/A
2063114Sgblack@eecs.umich.edu        # no carriage returns
207511SN/A        if line.find('\r') != -1:
2085513SMichael.Adler@intel.com            self.cret += 1
2095513SMichael.Adler@intel.com            if verbose > 1:
2105513SMichael.Adler@intel.com                msg(i, line, 'carriage return found')
2115513SMichael.Adler@intel.com            bad()
212511SN/A
2131706SN/A        # lines max out at 79 chars
2143114Sgblack@eecs.umich.edu        llen = linelen(line)
2151706SN/A        if llen > 79:
2161706SN/A            stats.toolong += 1
2171706SN/A            if llen == 80:
2181706SN/A                stats.toolong80 += 1
2193114Sgblack@eecs.umich.edu            if verbose > 1:
2201706SN/A                msg(i, line, 'line too long (%d chars)' % llen)
2211706SN/A            bad()
2221706SN/A
2231706SN/A        # no tabs used to indent
2243114Sgblack@eecs.umich.edu        match = lead.search(line)
2251706SN/A        if match and match.group(1).find('\t') != -1:
226511SN/A            stats.leadtabs += 1
2276703Svince@csl.cornell.edu            if verbose > 1:
2286703Svince@csl.cornell.edu                msg(i, line, 'using tabs to indent')
2296703Svince@csl.cornell.edu            bad()
2306703Svince@csl.cornell.edu
2316685Stjones1@inf.ed.ac.uk        # no trailing whitespace
2326685Stjones1@inf.ed.ac.uk        if trail.search(line):
2336685Stjones1@inf.ed.ac.uk            stats.trailwhite +=1
2346685Stjones1@inf.ed.ac.uk            if verbose > 1:
2356685Stjones1@inf.ed.ac.uk                msg(i, line, 'trailing whitespace')
2365513SMichael.Adler@intel.com            bad()
2375513SMichael.Adler@intel.com
2385513SMichael.Adler@intel.com        # for c++, exactly one space betwen if/while/for and (
2395513SMichael.Adler@intel.com        if cpp:
2405513SMichael.Adler@intel.com            match = any_control.search(line)
2411999SN/A            if match and not good_control.search(line):
2421999SN/A                stats.badcontrol += 1
2433114Sgblack@eecs.umich.edu                if verbose > 1:
2441999SN/A                    msg(i, line, 'improper spacing after %s' % match.group(1))
2451999SN/A                bad()
2461999SN/A
2471999SN/Adef modified_lines(old_data, new_data, max_lines):
2483114Sgblack@eecs.umich.edu    from itertools import count
2491999SN/A    from mercurial import bdiff, mdiff
2503079Sstever@eecs.umich.edu
2513079Sstever@eecs.umich.edu    modified = set()
2523114Sgblack@eecs.umich.edu    counter = count()
2533079Sstever@eecs.umich.edu    for pbeg, pend, fbeg, fend in bdiff.blocks(old_data, new_data):
2542093SN/A        for i in counter:
2552093SN/A            if i < fbeg:
2563114Sgblack@eecs.umich.edu                modified.add(i)
2572093SN/A            elif i + 1 >= fend:
2582687Sksewell@umich.edu                break
2592687Sksewell@umich.edu            elif i > max_lines:
2603114Sgblack@eecs.umich.edu                break
2612687Sksewell@umich.edu    return modified
2622238SN/A
2632238SN/Adef check_whitespace(ui, repo, hooktype, node, parent1, parent2):
2643114Sgblack@eecs.umich.edu    from mercurial import mdiff
2652238SN/A
2662238SN/A    if hooktype != 'pretxncommit':
2672238SN/A        raise AttributeError, \
2683114Sgblack@eecs.umich.edu              "This hook is only meant for pretxncommit, not %s" % hooktype
2692238SN/A
2702238SN/A    tabsize = 8
2712238SN/A    verbose = ui.configbool('style', 'verbose', False)
2723114Sgblack@eecs.umich.edu    def prompt(name, fixonly=None):
2732238SN/A        result = ui.prompt("(a)bort, (i)gnore, or (f)ix?", "^[aif]$", "a")
2742238SN/A        if result == 'a':
2752238SN/A            return True
2763114Sgblack@eecs.umich.edu        elif result == 'i':
2772238SN/A            pass
2782238SN/A        elif result == 'f':
2792238SN/A            fixwhite(repo.wjoin(name), tabsize, fixonly)
2803114Sgblack@eecs.umich.edu        else:
2812238SN/A            raise RepoError, "Invalid response: '%s'" % result
2822238SN/A
2832238SN/A        return False
2843114Sgblack@eecs.umich.edu
2852238SN/A    modified, added, removed, deleted, unknown, ignore, clean = repo.status()
2862238SN/A
2872238SN/A    for fname in added:
2883114Sgblack@eecs.umich.edu        ok = True
2892238SN/A        for line,num in checkwhite(fname):
2906109Ssanchezd@stanford.edu            ui.write("invalid whitespace in %s:%d\n" % (fname, num))
2916109Ssanchezd@stanford.edu            if verbose:
2926109Ssanchezd@stanford.edu                ui.write(">>%s<<\n" % line[-1])
2932238SN/A            ok = False
2949455Smitch.hayenga+gem5@gmail.com
2959455Smitch.hayenga+gem5@gmail.com        if not ok:
2969455Smitch.hayenga+gem5@gmail.com            if prompt(fname):
29710203SAli.Saidi@ARM.com                return True
29810203SAli.Saidi@ARM.com
29910203SAli.Saidi@ARM.com    wctx = repo.workingctx()
3009455Smitch.hayenga+gem5@gmail.com    for fname in modified:
3019112Smarc.orr@gmail.com        fctx = wctx.filectx(fname)
3029112Smarc.orr@gmail.com        pctx = fctx.parents()
3039112Smarc.orr@gmail.com        assert len(pctx) in (1, 2)
3049112Smarc.orr@gmail.com
3059112Smarc.orr@gmail.com        file_data = fctx.data()
3069112Smarc.orr@gmail.com        lines = mdiff.splitnewlines(file_data)
3079112Smarc.orr@gmail.com        mod_lines = modified_lines(pctx[0].data(), file_data, len(lines))
3089112Smarc.orr@gmail.com        if len(pctx) == 2:
3099112Smarc.orr@gmail.com            m2 = modified_lines(pctx[1].data(), file_data, len(lines))
3109112Smarc.orr@gmail.com            mod_lines = mod_lines & m2 # only the lines that are new in both
3119112Smarc.orr@gmail.com
3129112Smarc.orr@gmail.com        fixonly = set()
3139112Smarc.orr@gmail.com        for i,line in enumerate(lines):
3149112Smarc.orr@gmail.com            if i not in mod_lines:
3159112Smarc.orr@gmail.com                continue
3169112Smarc.orr@gmail.com
3179112Smarc.orr@gmail.com            if checkwhite_line(line):
3189112Smarc.orr@gmail.com                continue
3199112Smarc.orr@gmail.com
3209112Smarc.orr@gmail.com            ui.write("invalid whitespace: %s:%d\n" % (fname, i+1))
3219112Smarc.orr@gmail.com            if verbose:
3229112Smarc.orr@gmail.com                ui.write(">>%s<<\n" % line[:-1])
3239112Smarc.orr@gmail.com            fixonly.add(i)
3249112Smarc.orr@gmail.com
3259238Slluc.alvarez@bsc.es        if fixonly:
3269112Smarc.orr@gmail.com            if prompt(fname, fixonly):
3279112Smarc.orr@gmail.com                return True
3289112Smarc.orr@gmail.com
3299112Smarc.orr@gmail.comdef check_format(ui, repo, hooktype, node, parent1, parent2):
3309112Smarc.orr@gmail.com    if hooktype != 'pretxncommit':
3319112Smarc.orr@gmail.com        raise AttributeError, \
3329112Smarc.orr@gmail.com              "This hook is only meant for pretxncommit, not %s" % hooktype
3339112Smarc.orr@gmail.com
3349112Smarc.orr@gmail.com    modified, added, removed, deleted, unknown, ignore, clean = repo.status()
3359112Smarc.orr@gmail.com
3369112Smarc.orr@gmail.com    verbose = 0
3379112Smarc.orr@gmail.com    stats = ValidationStats()
3389112Smarc.orr@gmail.com    for f in modified + added:
3399112Smarc.orr@gmail.com        validate(f, stats, verbose, None)
3409112Smarc.orr@gmail.com
3419112Smarc.orr@gmail.com    if stats:
3429112Smarc.orr@gmail.com        stats.dump()
3439112Smarc.orr@gmail.com        result = ui.prompt("invalid formatting\n(i)gnore or (a)bort?",
3449112Smarc.orr@gmail.com                           "^[ia]$", "a")
3459112Smarc.orr@gmail.com        if result.startswith('i'):
3469112Smarc.orr@gmail.com            pass
3479112Smarc.orr@gmail.com        elif result.startswith('a'):
3489112Smarc.orr@gmail.com            return True
3499112Smarc.orr@gmail.com        else:
3509112Smarc.orr@gmail.com            raise RepoError, "Invalid response: '%s'" % result
3519112Smarc.orr@gmail.com
3529112Smarc.orr@gmail.com    return False
3539112Smarc.orr@gmail.com
3549112Smarc.orr@gmail.comif __name__ == '__main__':
3559112Smarc.orr@gmail.com    import getopt
3569112Smarc.orr@gmail.com
3579112Smarc.orr@gmail.com    progname = sys.argv[0]
3589112Smarc.orr@gmail.com    if len(sys.argv) < 2:
3599112Smarc.orr@gmail.com        sys.exit('usage: %s <command> [<command args>]' % progname)
3609112Smarc.orr@gmail.com
3619112Smarc.orr@gmail.com    fixwhite_usage = '%s fixwhite [-t <tabsize> ] <path> [...] \n' % progname
3629112Smarc.orr@gmail.com    chkformat_usage = '%s chkformat <path> [...] \n' % progname
3639112Smarc.orr@gmail.com    chkwhite_usage = '%s chkwhite <path> [...] \n' % progname
3649112Smarc.orr@gmail.com
3659112Smarc.orr@gmail.com    command = sys.argv[1]
3669112Smarc.orr@gmail.com    if command == 'fixwhite':
3679112Smarc.orr@gmail.com        flags = 't:'
3689112Smarc.orr@gmail.com        usage = fixwhite_usage
3699112Smarc.orr@gmail.com    elif command == 'chkwhite':
3709112Smarc.orr@gmail.com        flags = 'nv'
3719112Smarc.orr@gmail.com        usage = chkwhite_usage
3729112Smarc.orr@gmail.com    elif command == 'chkformat':
3739112Smarc.orr@gmail.com        flags = 'nv'
3749112Smarc.orr@gmail.com        usage = chkformat_usage
3759112Smarc.orr@gmail.com    else:
3769112Smarc.orr@gmail.com        sys.exit(fixwhite_usage + chkwhite_usage + chkformat_usage)
3779238Slluc.alvarez@bsc.es
3789112Smarc.orr@gmail.com    opts, args = getopt.getopt(sys.argv[2:], flags)
3799112Smarc.orr@gmail.com
3809112Smarc.orr@gmail.com    code = 1
3819112Smarc.orr@gmail.com    verbose = 1
3829112Smarc.orr@gmail.com    tabsize = 8
3832238SN/A    for opt,arg in opts:
3842238SN/A        if opt == '-n':
3852238SN/A            code = None
3862238SN/A        if opt == '-t':
3873114Sgblack@eecs.umich.edu            tabsize = int(arg)
3882238SN/A        if opt == '-v':
3892238SN/A            verbose += 1
3902238SN/A
3913114Sgblack@eecs.umich.edu    if command == 'fixwhite':
3922238SN/A        for filename in args:
3932238SN/A            fixwhite(filename, tabsize)
3942238SN/A    elif command == 'chkwhite':
3953114Sgblack@eecs.umich.edu        for filename in args:
3962238SN/A            for line,num in checkwhite(filename):
3972238SN/A                print 'invalid whitespace: %s:%d' % (filename, num)
3982238SN/A                if verbose:
3993114Sgblack@eecs.umich.edu                    print '>>%s<<' % line[:-1]
4002238SN/A    elif command == 'chkformat':
4012238SN/A        stats = ValidationStats()
4021354SN/A        for filename in files:
4031354SN/A            validate(filename, stats=stats, verbose=verbose, exit_code=code)
40410796Sbrandon.potter@amd.com
40510796Sbrandon.potter@amd.com        if verbose > 0:
4061354SN/A            stats.dump()
4071354SN/A    else:
4081354SN/A        sys.exit("command '%s' not found" % command)
4091354SN/A