Count the number of lines (code, comment, blank) in one or several Python source files.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | #! /usr/bin/env python
# -*- coding: utf-8 -*-
# <https://code.activestate.com/recipes/580709-lines-of-code-loc/>
# Basic Lines-Of-Code counter in Python source files, reporting the
# number of blank, comment and source code lines and total number of
# lines in all Python files scanned.
# Usage example:
# % python locs.py -rec ~/Projects
# 8691 *.py files: 365038 blank (14.0%), 212100 comment (8.1%),
# 2030198 source (77.9%), 2607336 total lines
# (2.739 secs, 951872 lines/sec)
# % python3 locs.py -rec ~/Projects
# 8691 *.py files: 365037 blank (14.0%), 212100 comment (8.1%),
# 2030198 source (77.9%), 2607335 total lines
# (2.599 secs, 1003158 lines/sec)
# % python3 locs.py -h
# usage: locs.py [-help] [-recurse] [-verbose] <file_or_dir_name> ...
# Tested with 64-bit Python 2.7.10 and 3.5.1 on MacOS 10.11.6 only.
from glob import iglob
from os.path import basename, exists, isdir, join
from time import time
__all__ = ('Loc',)
__version__ = '16.10.25'
class Loc(object):
'''Lines-Of-Code accumulator.
'''
blank = 0
comment = 0
files = 0
source = 0
ext = '.py'
_time0 = 0
_recurse = False # process dirs
_verbose = False # print details
def __init__(self, recurse=False, verbose=False):
if recurse:
self._recurse = recurse
if verbose:
self._verbose = verbose
self._time0 = time()
def __str__(self):
s = time() - self._time0
n = self.source + self.comment + self.blank
p = int(n / s) if n > s > 0 else '-'
t = ['%s *%s files:' % (self.files, self.ext),
self._bcst(self.blank, self.comment, self.source),
'(%.3f secs, %s lines/sec)' % (s, p)]
return ' '.join(t)
def _bcst(self, blank, comment, source):
t, n = [], blank + comment + source
for a, v in (('blank', blank),
('comment', comment),
('source', source)):
p = ' (%.1f%%)' % ((v * 100.0) / n,) if n > 0 else ''
t.append('%s %s%s' % (v, a, p))
t.append('%s total lines' % (n,))
return ', '.join(t)
def adir(self, name):
'''Process a directory.
'''
if self._recurse:
if self._verbose:
print(' dir %s: %s' % (name, '...'))
b, c, s = self.blank, self.comment, self.source
self.aglob(join(name, '*'))
b = self.blank - b
c = self.comment - c
s = self.source - s
t = name, self._bcst(b, c, s)
print(' dir %s: %s' % t)
else:
self.aglob(join(name, '*'))
def afile(self, name):
'''Process a file.
'''
if name.endswith(self.ext) and exists(name):
b = c = s = 0
with open(name, 'rb') as f:
for t in f.readlines():
t = t.lstrip()
if not t:
b += 1
elif t.startswith(b'#'): # Python 3+
c += 1
else:
s += 1
self.blank += b
self.comment += c
self.source += s
self.files += 1
if self._verbose:
t = self.files, name, self._bcst(b, c, s)
print('file %s %s: %s' % t)
def aglob(self, wild):
'''Process a possible wildcard.
'''
for t in iglob(wild):
if isdir(t):
self.adir(t)
else:
self.afile(t)
if __name__ == '__main__':
import sys
argv0 = basename(sys.argv[0])
loc = Loc()
try:
for arg in sys.argv[1:]:
if not arg.startswith('-'):
loc.aglob(arg)
elif '-help'.startswith(arg):
print('usage: %s [-help] [-recurse] [-verbose] <file_or_dir_name> ...' % (argv0,))
sys.exit(0)
elif '-recurse'.startswith(arg):
loc._recurse = True
elif '-verbose'.startswith(arg):
loc._verbose = True
elif arg != '--':
print('%s: invalid option: %r' % (argv0, arg))
sys.exit(1)
except KeyboardInterrupt:
print('')
print('%s' % (loc,))
|
Updated to report details per file and directory with command line option -verbose.