Welcome, guest | Sign In | My Account | Store | Cart
#! /usr/bin/env python
# -*- coding: utf-8 -*-

# Basic Lines-Of-Code counter in Python source files, reporting the
# number of source code lines, comment lines, blank lines and total
# number of lines in all Python files scanned.

# Usage example:

# % python locs.py -rec ~/Projects
# locs.py: 8689 *.py files: 2030008 source (77.9%), 212064 comment (8.1%),
#                            364984 blank (14.0%), 2607056 total lines
#                           (3.393 secs, 768473 lines/sec)

# % python3 locs.py -rec ~/Projects
# locs.py: 8689 *.py files: 2030008 source (77.9%), 212064 comment (8.1%),
#                            364984 blank (14.0%), 2607056 total lines
#                           (3.029 secs, 860799 lines/sec)

# % python3 locs.py -h
# usage: locs.py [-help] [-log] [-recurse] <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.24'


class Loc(object):
    '''Lines-Of-Code accumulator.
    '''
    blank   = 0
    comment = 0
    files   = 0
    source  = 0

    ext = '.py'
    log = False  # print process
    rec = False  # recursively process dirs
    _t0 = 0

    def __init__(self, log=False, rec=False):
        if log:
            self.log = log
        if rec:
            self.rec = rec
        self._t0 = time()

    def __str__(self):
        n = self.source + self.comment + self.blank
        s = time() - self._t0
        t = ['%s *%s files:' % (self.files, self.ext)]
        for a in ('source', 'comment', 'blank'):
            v = getattr(self, a)
            if n > 0:
                p = ' (%.1f%%)' % ((v * 100.0) / n,)
            else:
                p = ''
            t.append('%s %s%s,' % (v, a, p))
        p = int(n / s)
        t.append('%s total lines (%.3f secs, %s lines/sec)' % (n, s, p))
        return ' '.join(t)

    def adir(self, name):
        '''Process a directory.
        '''
        if self.rec:
            self.aglob(join(name, '*'))

    def afile(self, name):
        '''Process a file.
        '''
        if name.endswith(self.ext) and exists(name):
            self.files += 1
            if self.log:
                print('file %s: %s' % (self.files, name))
            with open(name, 'rb') as f:
                for t in f.readlines():
                    t = t.strip()
                    if t.startswith(b'#'):  # Python 3+
                        self.comment += 1
                    elif t:
                        self.source += 1
                    else:
                        self.blank += 1

    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] [-log] [-recurse] <file_or_dir_name> ...' % (argv0,))
                sys.exit(0)
            elif '-log'.startswith(arg):
                loc.log = True
            elif '-recurse'.startswith(arg):
                loc.rec = True
            elif arg != '--':
                print('%s: invalid option: %r' % (argv0, arg))
                sys.exit(1)

    except KeyboardInterrupt:
        print('')

    print('%s: %s' % (argv0, loc))

History