#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
# 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] ...
# 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] ...' % (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,))