SirTony
3/2/2015 - 7:59 PM

A grep-like tool written in D.

A grep-like tool written in D.

module grep;

private:

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

bool recursive;
bool ignoreCase;
Regex!char searchRegex;
Regex!char fileRegex;

enum string usage = q{grep.d - Tony Hudgins (c) 2015

  A small command-line utility for matching file contents.

  Usage: grep [flags...] <file filter> <regex pattern>

  Flags can be any combination of the following:

    --help,        -h | Show this help message.
    --ignore-case, -i | Perform a case-insensitive match.
    --recursive,   -r | Recursively search subdirectories for matching files.
};

int main( string[] args )
{
    try
    {
        bool help;
        
        args.getopt(
            config.caseInsensitive,
            config.bundling,
            
            "help|h",        &help,
            "recursive|r",   &recursive,
            "ignore-case|i", &ignoreCase
        );
        
        if( help )
        {
            stderr.writeln( usage );
            return -1;
        }
    }
    catch( GetOptException e )
    {
        stderr.writeln( e.msg );
        return 5;
    }
    
    if( args.length > 0 )
        args.popFront();
    
    if( args.length == 0 )
    {
        stderr.writeln( usage );
        return 3;
    }
    
    if( !args.front.verifyRegex( fileRegex ) )
        return 1;
    
    args.popFront();
    
    if( args.length == 0 )
    {
        stderr.writeln( usage );
        return 4;
    }
    
    if( !args.front.verifyRegex( searchRegex ) )
        return 2;
    
    args.popFront();
    
    auto entries = getcwd().dirEntries( recursive ? SpanMode.depth : SpanMode.shallow );
    
    foreach( entry; entries )
    {
        if( entry.isDir )
            continue;
        
        if( !entry.name.matchFirst( fileRegex ) )
            continue;
        
        auto path = buildNormalizedPath( getcwd(), entry.name );
        auto file = new io.File( path, io.FileMode.In );
        
        foreach( lineno, char[] line; file )
        {
            auto match = line.matchFirst( searchRegex );
            
            if( !match )
                continue;
            
            auto name = entry.name.replace( getcwd(), "" )[1 .. $];
            
            "%s:%s - %s".writefln( name, lineno, match.hit );
        }
    }
    
    return 0;
}

bool verifyRegex( string pattern, out Regex!char rex )
{
    try
    {
        rex = pattern.regex( ignoreCase ? "i" : "" );
        return true;
    }
    catch( RegexException e )
    {
        stderr.writeln( e.msg );
        return false;
    }
}