#!/usr/local/bin/perl
use strict;
use warnings;

use Getopt::Long;
use File::Spec;
use File::Find qw( find );
use File::Path qw( mkpath );
use File::stat;

my %done;
my ( $outdir, $charm_src );

GetOptions(
    'out=s' => \$outdir,
    'src=s' => \$charm_src,
);
$charm_src ||= File::Spec->rel2abs('src');

# Validate Charmonizer source dir.
my $probe_path
    = File::Spec->catfile( $charm_src, 'Charmonizer', 'Probe.harm' );
die("Can't find Charmonizer source tree -- was 'src' specified correctly?")
    unless -e $probe_path;

# Verify/create output directory.
die "Must specify output directory" unless defined $outdir;
mkpath($outdir)                     unless -d $outdir;

# Accumulate list of .harm/.charm files.
my @charm_files;
find(
    {   wanted => sub {
            my $name = $File::Find::name;
            return unless $name =~ s/.*?(Charmonizer.+\.c?harm$)/$1/;
            push @charm_files, $name;
        },
        no_chdir => 1,
    },
    $charm_src,
);

# Process all source files.
process_file($_) for @charm_files;
exit;

# Transfer a Charmonizer source file to the destination, transforming it from
# a .charm/.harm to a .c/.h and replacing all metaquotes.
sub process_file {
    my $rel_path = shift;
    return if $done{$rel_path};

    my $src_path = File::Spec->catfile( $charm_src, $rel_path );
    my $content  = slurp_file($src_path);
    $content = replace_metaquotes($content);
    my $dest_path = File::Spec->catfile( $outdir, $rel_path );
    $dest_path =~ s/(\.[ch])[^.]+$/$1/ or die "No match";

    # Don't generate new file if current.
    my $stat_orig = stat($src_path);
    my $stat_dest = stat($dest_path);
    return
        if ($stat_orig
        and $stat_dest
        and $stat_dest->mtime > $stat_orig->mtime );

    print "Writing $dest_path\n";
    spit_file( $dest_path, $content );
    $done{$rel_path} = 1;
}

sub replace_metaquotes {
    my $content = shift;
    while ( $content =~ m/METAQUOTE(.+?)METAQUOTE/s ) {
        my $metaquoted = $1;
        $metaquoted =~ s/\\/\\\\/g;
        $metaquoted =~ s/"/\\"/g;
        $metaquoted =~ s/\n/\\n"\n    "/mg;
        $content    =~ s/METAQUOTE(.+?)METAQUOTE/"$metaquoted"/s
            or die "no match";
    }
    return $content;
}

sub slurp_file {
    my $path = shift;
    open( my $fh, '<', $path ) or die "Can't open '$path': $!";
    my $content = do { local $/; <$fh> };
    close $fh or die "Can't close '$path': $!";
    return $content;
}

sub spit_file {
    my ( $path, $content ) = @_;
    my ( undef, $dest_dir, undef ) = File::Spec->splitpath($path);
    mkpath($dest_dir);
    open( my $fh, '>', $path ) or die "Can't open '$path': $!";
    binmode $fh;
    print $fh $content;
    close $fh or die "Can't close '$path': $!";
}


__END__

=head1 NAME

metaquote - Translate Charmonizer modules to C.

=head1 SYNOPSIS

   $ ./bin/metaquote --out=/path/to/output_charm_dir \
   > --src=/path/to/charmonizer/src

=head1 DESCRIPTION

The C<metaquote> utility processes a directory of Charmonizer .harm/.charm
source files, replacing METAQUOTE-delimited text with legal C double-quoted
text and generating .h/.c files.

=head1 ARGUMENTS

=over

=item --out=DEST_DIR

The directory where the extracted .c/.h Charmonizer module files will be
written.

=item --src=SOURCE_DIR

The Charmonizer source directory.

=back

=head1 COPYRIGHT & LICENSE

Copyright 2006-2009 Marvin Humphrey

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

=cut
