715d20720c98cc6b546faabaf2890508149525a2
[scons.git] / bin / linecount.py
1 #!/usr/bin/env python
2 #
3 # __COPYRIGHT__
4 #
5 # Count statistics about SCons test and source files.  This must be run
6 # against a fully-populated tree (for example, one that's been freshly
7 # checked out).
8 #
9 # A test file is anything under the src/ directory that begins with
10 # 'test_' or ends in 'Tests.py', or anything under the test/ directory
11 # that ends in '.py'.  Note that runtest.py script does *not*, by default,
12 # consider the files that begin with 'test_' to be tests, because they're
13 # tests of SCons packaging and installation, not functional tests of
14 # SCons code.
15 #
16 # A source file is anything under the src/engine/ or src/script/
17 # directories that ends in '.py' but does NOT begin with 'test_'
18 # or end in 'Tests.py'.
19 #
20 # We report the number of tests and sources, the total number of lines
21 # in each category, the number of non-blank lines, and the number of
22 # non-comment lines.  The last figure (non-comment) lines is the most
23 # interesting one for most purposes.
24 #
25 from __future__ import generators  ### KEEP FOR COMPATIBILITY FIXERS
26
27 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
28
29 import os.path
30
31 fmt = "%-16s  %5s  %7s  %9s  %11s  %11s"
32
33 class Collection(object):
34   def __init__(self, name, files=None, pred=None):
35     self._name = name
36     if files is None:
37       files = []
38     self.files = files
39     if pred is None:
40       pred = lambda x: True
41     self.pred = pred
42   def __call__(self, fname):
43     return self.pred(fname)
44   def __len__(self):
45     return len(self.files)
46   def extend(self, files):
47     self.files.extend(files)
48   def lines(self):
49     try:
50       return self._lines
51     except AttributeError:
52       self._lines = lines = []
53       for file in self.files:
54           file_lines = open(file).readlines()
55           lines.extend([s.lstrip() for s in file_lines])
56       return lines
57   def non_blank(self):
58     return [s for s in self.lines() if s != '']
59   def non_comment(self):
60     return [s for s in self.lines() if s == '' or s[0] != '#']
61   def non_blank_non_comment(self):
62     return [s for s in self.lines() if s != '' and s[0] != '#']
63   def printables(self):
64     return (self._name + ':',
65             len(self.files),
66             len(self.lines()),
67             len(self.non_blank()),
68             len(self.non_comment()),
69             len(self.non_blank_non_comment()))
70
71 def is_Tests_py(x):
72     return x[-8:] == 'Tests.py'
73 def is_test_(x):
74     return x[:5] == 'test_'
75 def is_python(x):
76     return x[-3:] == '.py'
77 def is_source(x):
78     return is_python(x) and not is_Tests_py(x) and not is_test_(x)
79
80 src_Tests_py_tests = Collection('src/ *Tests.py', pred=is_Tests_py)
81 src_test_tests = Collection('src/ test_*.py', pred=is_test_)
82 test_tests = Collection('test/ tests', pred=is_python)
83 sources = Collection('sources', pred=is_source)
84
85 def t(arg, dirname, names):
86     try: names.remove('.svn')
87     except ValueError: pass
88     names = list(filter(arg, names))
89     arg.extend([os.path.join(dirname, n) for n in names])
90
91 os.path.walk('src', t, src_Tests_py_tests)
92 os.path.walk('src', t, src_test_tests)
93 os.path.walk('test', t, test_tests)
94 os.path.walk('src/engine', t, sources)
95 os.path.walk('src/script', t, sources)
96
97 src_tests = Collection('src/ tests', src_Tests_py_tests.files
98                                      + src_test_tests.files)
99 all_tests = Collection('all tests', src_tests.files + test_tests.files)
100
101 def ratio(over, under):
102     return "%.2f" % (float(len(over)) / float(len(under)))
103
104 print fmt % ('', '', '', '', '', 'non-blank')
105 print fmt % ('', 'files', 'lines', 'non-blank', 'non-comment', 'non-comment')
106 print
107 print fmt % src_Tests_py_tests.printables()
108 print fmt % src_test_tests.printables()
109 print
110 print fmt % src_tests.printables()
111 print fmt % test_tests.printables()
112 print
113 print fmt % all_tests.printables()
114 print fmt % sources.printables()
115 print
116 print fmt % ('ratio:',
117              ratio(all_tests, sources),
118              ratio(all_tests.lines(), sources.lines()),
119              ratio(all_tests.non_blank(), sources.non_blank()),
120              ratio(all_tests.non_comment(), sources.non_comment()),
121              ratio(all_tests.non_blank_non_comment(),
122                    sources.non_blank_non_comment())
123             )