/conv.pl
#!/usr/bin/perl
use Getopt::Std;
use File::Basename;
use strict;
use warnings;

my %rules;
my %vars;
my %opts = (
    j => 1
);

getopts('vnsj:f:o:i:', \%opts);

my $dirout;
if (defined $opts{o}) {
    if (-d $opts{o}) {
        # output is a directory
        $dirout = 1;
    } else {
        # output is a single file, imply -i <ext> unless -i has also been specified
        if ($opts{o} =~ /\.(\w+)$/) {
            $opts{i} = $1 unless exists $opts{i};
        } else {
            print "Specified -o <file>, but '$opts{o}' has no extension.\n";
            exit 1;
        }
    }
}

print "Using $opts{j} processors\n" if $opts{v};

my $var_re = qr/[A-Za-z_][A-Za-z0-9_]*/;

sub convert {
    my ($base, $iext, $oext) = @_;
    my $cmd = $rules{$oext}{$iext};
    my $infile = "$base.$iext";
    $infile =~ s/"/\\"/g;
    $cmd =~ s!\$\<!"$infile"!g;
    $cmd =~ s!\$\*!"$base"!g;
    my $outfile;
    if ($opts{o}) {
        if ($dirout) {
            # Strip leading path from base so we can stick the file in
            # another dir.
            $base = basename($base);
            $outfile = "$opts{o}/$base.$oext";
        } else {
            $outfile = $opts{o};
        }
    } else {
        $outfile = "$base.$oext";
    }
    $outfile =~ s/"/\\"/g;
    $cmd =~ s!\$\@!"$outfile"!g;
    $cmd = expand($cmd);
    print "$cmd\n";
    return if $opts{n};
    my $pid = fork();
    return $pid if $pid;

    exec($cmd);
}

sub expand {
    local $_ = shift;
    s/\$($var_re)/(exists $vars{$1} ? $vars{$1} : '$' . $1)/eg;
    return $_;
}

open CONF, ($opts{f} || 'Convfile') or die "Could not open Convfile";
while (<CONF>) {
    s/#.*$//;
    s/^\s+//;
    s/\s+$//;
    next if /^$/;

    if (/^([\w.-]+)\s*=>\s*([\w.-]+)\s*:\s*(.*)$/) {
        $rules{$2}{$1} = $3;
    } elsif (/^($var_re)\s*=\s*(.*)$/) {
        $vars{$1} = expand($2);
    } else {
        warn "Error in Convfile line $.: $_\n";
    }
}
close CONF;

my @queue;
my @files;

# If we specify reading from stdin, do this.
if (exists $opts{s}) {
    while (<STDIN>) {
        next if /^#/;
        chomp;

        push @files, $_;
    }
} else {
    @files = @ARGV;
}

# Reversed semantics. Files are input to be converted to the specified extension
if ($opts{i}) {
    unless (exists $rules{$opts{i}}) {
        print <<EOD;
You have specified -i <ext>, but the Convfile has no rule to convert to
extension '$opts{i}'
EOD
        exit 1;
    }
    foreach (@files) {
        unless (-e $_) {
            print "$_ does not exist.\n";
            next;
        }

        if (/^(.+)\.(\w+)$/) {
            my $base = $1;
            my $iext = $2;
            print "Considering $base.$iext\n" if $opts{v};
            if (exists $rules{$opts{i}}{$iext}) {
                print "Using $base.$iext\n" if $opts{v};
                push @queue, [$base, $iext, $opts{i}];
            }
        } else {
            print "I don't see an extension on $_, so I don't know what to do with it.\n";
        }
    }
}

# Make semantics, files specified are desired output files
else {
    # Do something fun (convert *.foo into a list of files that would match if they existed)
    my @tmpfiles;
    foreach (@files) {
        if (/^\*\.(\w+)$/) {
            my $extsub = $1;
            push @tmpfiles,
            map { s/\.(\w+)$/\.$extsub/; $_ } 
                grep { /^[^.]/ }
                <*>;
        } else {
            push @tmpfiles, $_;
        }
    }
    @files = @tmpfiles;
    
    # I like to call this the TOWER OF POWER
    for my $ofile (@files) {
        my $ofound = 0;
        for my $oext (keys %rules) {
            my $oext_re = $oext;
            $oext_re =~ s/\./\\./g;
            if ($ofile =~ /^(.*)\.$oext_re$/) {
                my $base = $1;
                my $ifound = 0;
                for my $iext (keys %{$rules{$oext}}) {
                    print "Considering $base.$iext\n" if $opts{v};
                    if (-e "$base.$iext") {
                        print "Using $base.$iext\n" if $opts{v};
                        push @queue, [$base, $iext, $oext];
                        $ifound = 1;
                        last;
                    }
                }
                unless ($ifound) {
                    print "No suitable input file found for $ofile\n";
                }
                $ofound = 1;
                last;
            }
        }
        unless ($ofound) {
            print "No rule to make $ofile\n";
        }
    }
}

print scalar @queue, " items to process\n" if $opts{v};

my $procs = 0;
foreach my $chunk (@queue) {
    convert(@$chunk);
    $procs++;
    if ($procs == $opts{j}) {
        wait;
        $procs--;
    }
}
wait while $procs--;

# vim:ts=4 sts=4 sw=4 expandtab