]> git.lizzy.rs Git - plan9front.git/blob - sys/lib/python/tabnanny.py
igfx: fix sandybridge fdi link training bits and ordering
[plan9front.git] / sys / lib / python / tabnanny.py
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 tokeneater() 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()