Category: Code
Author/Contact Info psychotic
Description: Hello fellow monks. I have only recently started to walk the path of Perl enlightement, what a great resource PerlMonks is! Having said that, i'd like to contribute some code i wrote last night.

As i am working on a personal little project, i wanted to be able to check its progress in terms of lines of code written. I also wanted to be able to tell how much of the total lines of code comprising the project were comments. So i hacked together a small script. I've tried to reuse as much code as possible, so you'll notice my using of at least one Perl idiom i picked up from here.

Looking at the code itself, i think it is self-documenting. The only thing i'd like to comment on, is the initially wasteful look of slurping the entire file in prior to processing. I did that so it would be easier in the future to add support for parsing out POD, or doing other multi-line filtering. That is the only caveat i am aware of, POD currently counts as code. Thit's acceptable for my needs at this time, but of course you mileage *will* vary.

Usage is simple. Edit the list of extesions you want to be scanned for --inside the script-- to suit your liking. These are assumed to contain Perl code. If it is fed a directory as a first parameter, it will use that as the directory to start the recursive search, else it will use the current working directory. Of course searches inside every subdirectory and its subdirectories, etc.

I would appreciate any suggestions or comments that might be applicable. Feel free to bash me for any errors in either logic, style, or anything you will. Excuse the ugly presentation of results, as illustrated in the sample run below:

Code Cmts %Code Total File ====================================================================== +========== 51 4 7 55 /xxxxxxx.pl 14 2 12 16 /xxxxxxxxxxxxx.pl 64 14 18 78 /xxxxxxx.pl 14 3 18 17 /xxxxxxxxx.pl 48 2 4 50 /xxxxxxxx.pl 48 10 17 58 /xxxxxxxx.pl 17 2 11 19 /xxxxxx.pl 35 3 8 38 /xxxxxx.pl 25 4 14 29 /xxxxxxx.pl 18 5 22 23 /xxxxx.plex 108 15 12 123 /xxx.pl 166 37 18 203 /Infidel/xxxxxx.pm 20 2 9 22 /Infidel/xxxxxx.pm 118 22 16 140 /Infidel/xxxxxxx.pm 90 8 8 98 /Infidel/xxxx.pm 25 1 4 26 /Infidel/xxxxxx.pm 58 21 27 79 /Infidel/xxxxxxx.pm 70 42 38 112 /Infidel/xxxxxxxx.pm 40 5 11 45 /Infidel/xxxx.pm 6 2 25 8 /lines/test.pl ====================================================================== +========== SEARCHD: 'X:/projectOne/ooops/plex' RESULTS: 1035 LoC (1239 w/comments: 204 lines -> 16.46% of total)
Update I updated the code to add POD parsing (correct at least :)), and impoved a bit the readability of the results.
use strict;
use warnings;
use File::Find;
use Cwd;

our $dir = shift || cwd();
our @types = ('pl', 'plex', 'pm');

our @files;
find sub {
    return unless -f;
    my $fName = $_;
    foreach (@types) {
        my (undef, $ext) = split (/\./, $fName);
        $ext and push @files, $File::Find::name if $ext eq $_;
    }
}, $dir;

unless (@files) {
    die "No files found to operate on!";
}

print("  Code   Cmts  %Code  Total   File \n");
print "=" x 80 . "\n";

my ($TLOC, $TCOM, $TTOT) = (0,0,0);
foreach my $file (@files) {
    my ($LOC, $COM) = (0,0);
    
    my $contents = do { local (*ARGV, $/) = [$file] and <> };
    my @lines = grep { ! /^\s*$/ } split(/$\//, $contents);
    
    while (@lines) {
        $_ = shift @lines;
        if ( /^=[a-z0-9]+\s/ ) {
            do {
                $_ = shift @lines and $COM++;
            } until /^=cut/;
        }
        /^\s*\#/ ? undef $_ || $COM++ : $LOC++;
    }
    
    my $TOT = $LOC + $COM;
    
    $TLOC += $LOC;
    $TCOM += $COM;
    $TTOT += $TOT;
    
    my $PER = sprintf("%3s", sprintf("%.0f", ($COM/$TOT) * 100));
    
    printf("% 6d % 6d % 6d % 6d %s\n",
           $LOC, $COM, $PER, $TOT,
           '  '. substr $file, length($dir));
}

my $TPER = sprintf "%.2f", ($TCOM/$TTOT) * 100;
print "=" x 80 . "\n";
print "SEARCHD: '$dir'\n";
print "RESULTS: $TLOC LoC ($TTOT w/comments: $TCOM lines -> $TPER% of 
+total)";
Replies are listed 'Best First'.
Re: Perl project lines of code "analyzer"
by wazoox (Prior) on Dec 01, 2005 at 12:19 UTC
    In case you'd want something more complex, or working on different languages, have a look at SLOCcount.
Re: Perl project lines of code "analyzer"
by graq (Curate) on Dec 16, 2005 at 09:09 UTC

    I would consider replacing

    push @files, $File::Find::name if $ext eq $_;

    with

    push @files, $File::Find::name if( $ext and $ext eq $_ );

    in order avoid potential uninitialized value warnings.

      Updated to:
      $ext and push @files, $File::Find::name if $ext eq $_;
      Thanks for the tip! Working mostly on windows, i often forget that files may not have a type extension in other systems. Regards.

        Where you have...

        my $fname = $_; if (-f $fname) { foreach my $type (qw(pl pm)) { my (undef, $ext) = split (/\./, $fname); if (defined($ext) && $ext eq $type) { push(@files, $File::Find::name); } } }

        It would be better to instead have...

        my $fname = $_; if (-f $fname && /\.p(l|m)$/) { push(@files, $File::Find::name); }

        ...or so I believe. The reason I changed my own copy to this is that I have scripts with the version number at the end like so...

        Frans_Perl_Program.1.5.1.pl

        ...when I am working on something by stages.

Re: Perl project lines of code "analyzer"
by jdhedden (Deacon) on Dec 01, 2005 at 14:08 UTC
    Nice, but counts POD as code.

    Remember: There's always one more bug.
      I have already noted that as the only gotcha, in the description of the code. I also noted there that the mechanics are in place so one can add any multi-line filtering desired, since each file is first slurped. Thank you for commenting though!
        Opps, missed that. I got too anxious trying out the code. Anyways, I created my own version that includes POD counts. Thanks for your contribution, psychotic.
        #!/usr/bin/perl use strict; use warnings; use File::Find; MAIN: { # Top directory to look through my $dir = (@ARGV) ? shift : '.'; # Find Perl files in directory my @files; find sub { my $fname = $_; if (-f $fname) { foreach my $type (qw(pl pm)) { my (undef, $ext) = split (/\./, $fname); if (defined($ext) && $ext eq $type) { push(@files, $File::Find::name); } } } }, $dir; if (! @files) { print("No Perl files found in '$dir'\n"); exit(1); } # Header print(" Code Cmts POD Total File\n"); print("=" x 60, "\n"); # Process files and generate totals my ($tloc, $tcom, $ttot, $tpod) = (0,0,0,0); foreach my $file (@files) { my ($loc, $com, $pod) = (0,0,0); # Slurp file my $contents = do { local (*ARGV, $/) = [$file] and <> }; my @lines = split(/$\//, $contents); # Process lines while (@lines) { my $line = shift(@lines); # Ignore blank lines if ($line =~ /^\s*$/) { next; } # Count POD if ($line =~ /^=[a-z]/) { $pod++; while (@lines) { my $pline = shift(@lines); if ($pline =~ /^\s*$/) { next; # Ignore blank lines } $pod++; if ($pline =~ /^=cut/) { last; # Done with POD block } } } # Comments and code elsif ($line =~ /^\s*\#/) { $com++; } else { $loc++; } } # File results my $tot = $loc + $com + $pod; printf("% 5d % 5d % 5d % 5d %s\n", $loc, $com, $pod, $tot, $fi +le); # Totals $tloc += $loc; $tcom += $com; $tpod += $pod; $ttot += $tot; } # Print grand totals print("=" x 60, "\n"); printf("% 5d % 5d % 5d % 5d %s\n", $tloc, $tcom, $tpod, $ttot, '-- + Summary of all files'); } exit(0); # EOF
        UPDATE: Added correction suggested by psychotic.

        Remember: There's always one more bug.