SirTony
12/27/2014 - 11:29 PM

Counts all lines in source code files.

Counts all lines in source code files.

module lines;

import std.file;
import std.path;
import std.stdio;
import std.regex;
import std.getopt;
import std.string;
import std.traits;
import std.algorithm;
import io = std.stream;

/+
    Usage: lines [flags...]
    Flags:
        -d, --directory  [string] The directory where the source file are located (scanned recursively)
        -f, --filter     [string] A GLOB pattern to filter out any irrelevant files.
        -e, -[no]exclude [verb]   Whether or not to exclude empty lines from the final count.
+/

int main( string[] args )
{
    string sourceDir = null;
    string filters = "*";
    bool excludeEmptyLines = true;
    
    args.getopt(
        "d|directory", &sourceDir,
        "e|[no]empty", &excludeEmptyLines,
        "f|filter",    &filters,
    );
    
    if( sourceDir is null )
    {
        stderr.writeln( "Cannot find the path specified." );
        return 1;
    }
    
    sourceDir = sourceDir.absolutePath;
    
    if( !sourceDir.exists || !sourceDir.isDir )
    {
        stderr.writeln( "Cannot find the path specified." );
        return 1;
    }
    
    ulong files;
    ulong[] lines;

    try
    {
        auto entries = sourceDir.dirEntries( filters, SpanMode.breadth, true );
        
        foreach( e; entries )
        {
            if( !e.isFile )
                continue;
            
            auto f = new io.File( e.name, io.FileMode.In );
            foreach( char[] l; f )
            {
                if( excludeEmptyLines && l.strip().length == 0 )
                    continue;
                
                lines ~= l.length;
            }
            
            ++files;
        }
    }
    catch( Throwable th )
    {
        stderr.writefln( "Could not retrieve files. Reason: %s", th.msg );
        return 2;
    }
    
    ulong totalLines = lines.reduce!( ( x, y ) => x + y );
    "Found %s lines in %s files".writefln( lines.length.comify(), files.comify() );
    "Average line length: %s".writefln( lines.mean().comify() );
    
    return 0;
}

ulong mean( T )( T[] numbers ) if( isNumeric!T )
{
    auto sum = numbers.reduce!( ( x, y ) => x + y );
    return sum / numbers.length;
}

string comify( T )( T number ) if( isNumeric!T )
{
    auto rex = r"(?<=\d)(?=(\d\d\d)+\b)".regex( "g" );
    return "%s".format( number ).replaceAll( rex, "," );
}