Home | History | Annotate | Download | only in Lib
      1 #! /usr/bin/env python
      2 
      3 """The Tab Nanny despises ambiguous indentation.  She knows no mercy.
      4 
      5 tabnanny -- Detection of ambiguous indentation
      6 
      7 For the time being this module is intended to be called as a script.
      8 However it is possible to import it into an IDE and use the function
      9 check() described below.
     10 
     11 Warning: The API provided by this module is likely to change in future
     12 releases; such changes may not be backward compatible.
     13 """
     14 
     15 # Released to the public domain, by Tim Peters, 15 April 1998.
     16 
     17 # XXX Note: this is now a standard library module.
     18 # XXX The API needs to undergo changes however; the current code is too
     19 # XXX script-like.  This will be addressed later.
     20 
     21 __version__ = "6"
     22 
     23 import os
     24 import sys
     25 import getopt
     26 import tokenize
     27 if not hasattr(tokenize, 'NL'):
     28     raise ValueError("tokenize.NL doesn't exist -- tokenize module too old")
     29 
     30 __all__ = ["check", "NannyNag", "process_tokens"]
     31 
     32 verbose = 0
     33 filename_only = 0
     34 
     35 def errprint(*args):
     36     sep = ""
     37     for arg in args:
     38         sys.stderr.write(sep + str(arg))
     39         sep = " "
     40     sys.stderr.write("\n")
     41 
     42 def main():
     43     global verbose, filename_only
     44     try:
     45         opts, args = getopt.getopt(sys.argv[1:], "qv")
     46     except getopt.error, msg:
     47         errprint(msg)
     48         return
     49     for o, a in opts:
     50         if o == '-q':
     51             filename_only = filename_only + 1
     52         if o == '-v':
     53             verbose = verbose + 1
     54     if not args:
     55         errprint("Usage:", sys.argv[0], "[-v] file_or_directory ...")
     56         return
     57     for arg in args:
     58         check(arg)
     59 
     60 class NannyNag(Exception):
     61     """
     62     Raised by process_tokens() if detecting an ambiguous indent.
     63     Captured and handled in check().
     64     """
     65     def __init__(self, lineno, msg, line):
     66         self.lineno, self.msg, self.line = lineno, msg, line
     67     def get_lineno(self):
     68         return self.lineno
     69     def get_msg(self):
     70         return self.msg
     71     def get_line(self):
     72         return self.line
     73 
     74 def check(file):
     75     """check(file_or_dir)
     76 
     77     If file_or_dir is a directory and not a symbolic link, then recursively
     78     descend the directory tree named by file_or_dir, checking all .py files
     79     along the way. If file_or_dir is an ordinary Python source file, it is
     80     checked for whitespace related problems. The diagnostic messages are
     81     written to standard output using the print statement.
     82     """
     83 
     84     if os.path.isdir(file) and not os.path.islink(file):
     85         if verbose:
     86             print "%r: listing directory" % (file,)
     87         names = os.listdir(file)
     88         for name in names:
     89             fullname = os.path.join(file, name)
     90             if (os.path.isdir(fullname) and
     91                 not os.path.islink(fullname) or
     92                 os.path.normcase(name[-3:]) == ".py"):
     93                 check(fullname)
     94         return
     95 
     96     try:
     97         f = open(file)
     98     except IOError, msg:
     99         errprint("%r: I/O Error: %s" % (file, msg))
    100         return
    101 
    102     if verbose > 1:
    103         print "checking %r ..." % file
    104 
    105     try:
    106         process_tokens(tokenize.generate_tokens(f.readline))
    107 
    108     except tokenize.TokenError, msg:
    109         errprint("%r: Token Error: %s" % (file, msg))
    110         return
    111 
    112     except IndentationError, msg:
    113         errprint("%r: Indentation Error: %s" % (file, msg))
    114         return
    115 
    116     except NannyNag, nag:
    117         badline = nag.get_lineno()
    118         line = nag.get_line()
    119         if verbose:
    120             print "%r: *** Line %d: trouble in tab city! ***" % (file, badline)
    121             print "offending line: %r" % (line,)
    122             print nag.get_msg()
    123         else:
    124             if ' ' in file: file = '"' + file + '"'
    125             if filename_only: print file
    126             else: print file, badline, repr(line)
    127         return
    128 
    129     if verbose:
    130         print "%r: Clean bill of health." % (file,)
    131 
    132 class Whitespace:
    133     # the characters used for space and tab
    134     S, T = ' \t'
    135 
    136     # members:
    137     #   raw
    138     #       the original string
    139     #   n
    140     #       the number of leading whitespace characters in raw
    141     #   nt
    142     #       the number of tabs in raw[:n]
    143     #   norm
    144     #       the normal form as a pair (count, trailing), where:
    145     #       count
    146     #           a tuple such that raw[:n] contains count[i]
    147     #           instances of S * i + T
    148     #       trailing
    149     #           the number of trailing spaces in raw[:n]
    150     #       It's A Theorem that m.indent_level(t) ==
    151     #       n.indent_level(t) for all t >= 1 iff m.norm == n.norm.
    152     #   is_simple
    153     #       true iff raw[:n] is of the form (T*)(S*)
    154 
    155     def __init__(self, ws):
    156         self.raw  = ws
    157         S, T = Whitespace.S, Whitespace.T
    158         count = []
    159         b = n = nt = 0
    160         for ch in self.raw:
    161             if ch == S:
    162                 n = n + 1
    163                 b = b + 1
    164             elif ch == T:
    165                 n = n + 1
    166                 nt = nt + 1
    167                 if b >= len(count):
    168                     count = count + [0] * (b - len(count) + 1)
    169                 count[b] = count[b] + 1
    170                 b = 0
    171             else:
    172                 break
    173         self.n    = n
    174         self.nt   = nt
    175         self.norm = tuple(count), b
    176         self.is_simple = len(count) <= 1
    177 
    178     # return length of longest contiguous run of spaces (whether or not
    179     # preceding a tab)
    180     def longest_run_of_spaces(self):
    181         count, trailing = self.norm
    182         return max(len(count)-1, trailing)
    183 
    184     def indent_level(self, tabsize):
    185         # count, il = self.norm
    186         # for i in range(len(count)):
    187         #    if count[i]:
    188         #        il = il + (i/tabsize + 1)*tabsize * count[i]
    189         # return il
    190 
    191         # quicker:
    192         # il = trailing + sum (i/ts + 1)*ts*count[i] =
    193         # trailing + ts * sum (i/ts + 1)*count[i] =
    194         # trailing + ts * sum i/ts*count[i] + count[i] =
    195         # trailing + ts * [(sum i/ts*count[i]) + (sum count[i])] =
    196         # trailing + ts * [(sum i/ts*count[i]) + num_tabs]
    197         # and note that i/ts*count[i] is 0 when i < ts
    198 
    199         count, trailing = self.norm
    200         il = 0
    201         for i in range(tabsize, len(count)):
    202             il = il + i/tabsize * count[i]
    203         return trailing + tabsize * (il + self.nt)
    204 
    205     # return true iff self.indent_level(t) == other.indent_level(t)
    206     # for all t >= 1
    207     def equal(self, other):
    208         return self.norm == other.norm
    209 
    210     # return a list of tuples (ts, i1, i2) such that
    211     # i1 == self.indent_level(ts) != other.indent_level(ts) == i2.
    212     # Intended to be used after not self.equal(other) is known, in which
    213     # case it will return at least one witnessing tab size.
    214     def not_equal_witness(self, other):
    215         n = max(self.longest_run_of_spaces(),
    216                 other.longest_run_of_spaces()) + 1
    217         a = []
    218         for ts in range(1, n+1):
    219             if self.indent_level(ts) != other.indent_level(ts):
    220                 a.append( (ts,
    221                            self.indent_level(ts),
    222                            other.indent_level(ts)) )
    223         return a
    224 
    225     # Return True iff self.indent_level(t) < other.indent_level(t)
    226     # for all t >= 1.
    227     # The algorithm is due to Vincent Broman.
    228     # Easy to prove it's correct.
    229     # XXXpost that.
    230     # Trivial to prove n is sharp (consider T vs ST).
    231     # Unknown whether there's a faster general way.  I suspected so at
    232     # first, but no longer.
    233     # For the special (but common!) case where M and N are both of the
    234     # form (T*)(S*), M.less(N) iff M.len() < N.len() and
    235     # M.num_tabs() <= N.num_tabs(). Proof is easy but kinda long-winded.
    236     # XXXwrite that up.
    237     # Note that M is of the form (T*)(S*) iff len(M.norm[0]) <= 1.
    238     def less(self, other):
    239         if self.n >= other.n:
    240             return False
    241         if self.is_simple and other.is_simple:
    242             return self.nt <= other.nt
    243         n = max(self.longest_run_of_spaces(),
    244                 other.longest_run_of_spaces()) + 1
    245         # the self.n >= other.n test already did it for ts=1
    246         for ts in range(2, n+1):
    247             if self.indent_level(ts) >= other.indent_level(ts):
    248                 return False
    249         return True
    250 
    251     # return a list of tuples (ts, i1, i2) such that
    252     # i1 == self.indent_level(ts) >= other.indent_level(ts) == i2.
    253     # Intended to be used after not self.less(other) is known, in which
    254     # case it will return at least one witnessing tab size.
    255     def not_less_witness(self, other):
    256         n = max(self.longest_run_of_spaces(),
    257                 other.longest_run_of_spaces()) + 1
    258         a = []
    259         for ts in range(1, n+1):
    260             if self.indent_level(ts) >= other.indent_level(ts):
    261                 a.append( (ts,
    262                            self.indent_level(ts),
    263                            other.indent_level(ts)) )
    264         return a
    265 
    266 def format_witnesses(w):
    267     firsts = map(lambda tup: str(tup[0]), w)
    268     prefix = "at tab size"
    269     if len(w) > 1:
    270         prefix = prefix + "s"
    271     return prefix + " " + ', '.join(firsts)
    272 
    273 def process_tokens(tokens):
    274     INDENT = tokenize.INDENT
    275     DEDENT = tokenize.DEDENT
    276     NEWLINE = tokenize.NEWLINE
    277     JUNK = tokenize.COMMENT, tokenize.NL
    278     indents = [Whitespace("")]
    279     check_equal = 0
    280 
    281     for (type, token, start, end, line) in tokens:
    282         if type == NEWLINE:
    283             # a program statement, or ENDMARKER, will eventually follow,
    284             # after some (possibly empty) run of tokens of the form
    285             #     (NL | COMMENT)* (INDENT | DEDENT+)?
    286             # If an INDENT appears, setting check_equal is wrong, and will
    287             # be undone when we see the INDENT.
    288             check_equal = 1
    289 
    290         elif type == INDENT:
    291             check_equal = 0
    292             thisguy = Whitespace(token)
    293             if not indents[-1].less(thisguy):
    294                 witness = indents[-1].not_less_witness(thisguy)
    295                 msg = "indent not greater e.g. " + format_witnesses(witness)
    296                 raise NannyNag(start[0], msg, line)
    297             indents.append(thisguy)
    298 
    299         elif type == DEDENT:
    300             # there's nothing we need to check here!  what's important is
    301             # that when the run of DEDENTs ends, the indentation of the
    302             # program statement (or ENDMARKER) that triggered the run is
    303             # equal to what's left at the top of the indents stack
    304 
    305             # Ouch!  This assert triggers if the last line of the source
    306             # is indented *and* lacks a newline -- then DEDENTs pop out
    307             # of thin air.
    308             # assert check_equal  # else no earlier NEWLINE, or an earlier INDENT
    309             check_equal = 1
    310 
    311             del indents[-1]
    312 
    313         elif check_equal and type not in JUNK:
    314             # this is the first "real token" following a NEWLINE, so it
    315             # must be the first token of the next program statement, or an
    316             # ENDMARKER; the "line" argument exposes the leading whitespace
    317             # for this statement; in the case of ENDMARKER, line is an empty
    318             # string, so will properly match the empty string with which the
    319             # "indents" stack was seeded
    320             check_equal = 0
    321             thisguy = Whitespace(line)
    322             if not indents[-1].equal(thisguy):
    323                 witness = indents[-1].not_equal_witness(thisguy)
    324                 msg = "indent not equal e.g. " + format_witnesses(witness)
    325                 raise NannyNag(start[0], msg, line)
    326 
    327 
    328 if __name__ == '__main__':
    329     main()
    330