#!/usr/bin/perl -w

use Data::Dumper;
use File::Find;	   
use Getopt::Std;
use IO::File;
use strict;

my ($EXCLUDE, $HELP, $QUIET, $TEST);

my $usage = <<EOF;
Usage: $0 -hqt [-e <regexp>] <directory> [<directory>...]
-e <regexp>: Exclude paths matching <regexp> case-insensitive. e.g. "(.gif|.jpg)$"
-h: Display help message and exit
-q: Quiet mode, do not report normal processing of files
-t: Do not actually change files, just report what changes would be made
EOF

my $helpmsg = <<EOF;
This utility converts existing components to use new syntax
introduced in Mason 0.8.

1. Old-style mc_commands (mc_comp, mc_file, etc.) are converted to
new-style \$m methods (\$m->comp, \$m->file, etc.) See Commands.pod for
all the conversions to be performed.

2. References to request variable \$REQ are converted to \$m.

All directories will be traversed recursively.  We STRONGLY recommend
that you backup your components, and/or use the -t flag to preview,
before running this program for real.  Files are modified
destructively and no automatic backups are created.
EOF

my $warning = <<EOF;
Warning: All directories will be traversed recursively.  Files are
modified destructively and no automatic backups are created.
EOF

sub usage
{
    print $usage;
    exit;
}

sub main
{
    my (%opts);
    getopts('e:hlqtu',\%opts);
    ($EXCLUDE, $HELP, $QUIET, $TEST) = @opts{qw(e h q t)};
    if ($HELP) { print "$helpmsg\n$usage"; exit }
    if (!@ARGV) { print "$usage\n$helpmsg"; exit }
    my @dirs = @ARGV;
    
    if (!$TEST) {
	print "*** Mason 0.8 Conversion ***\n\n";
	print "Quiet mode.\n" if defined($QUIET);
	print "Excluding paths matching ($EXCLUDE).\n" if defined($EXCLUDE);
	print "Processing ".(@dirs==1 ? "directory " : "directories ").join(",",@dirs)."\n";
	print $warning;
	print "\nProceed? [n] ";
	exit if ((my $ans = <STDIN>) !~ /[Yy]/);
    }
    my $sub = sub {
	if (-f $_ && -s _) {
	    return if defined($EXCLUDE) && "$File::Find::dir/$_" =~ /$EXCLUDE/i;
	    convert($_,"$File::Find::dir/$_");
	}
    };
    find($sub,@dirs);
}

sub convert
{
    my ($file,$path) = @_;
    my $buf;
    my $infh = new IO::File $file;
    if (!$infh) { warn "cannot read $path: $!"; return }
    { local $/ = undef; $buf = <$infh> }

    my $c = 0;
    my (@changes,@failures);
    my $report = sub { push(@changes,$_[1] ? "$_[0]  -->  $_[1]" : "removed $_[0]") };
    my $report_failure = sub { push(@failures,$_[0]) };

    #
    # Convert mc_ commands to $m-> method equivalents
    #
    # Easy substitutions
    #
    my $easy_cmds = join("|",qw(abort cache cache_self call_self comp comp_exists dhandler_arg file file_root out time));
    if (!$TEST) {
	$c += ($buf =~ s{mc_($easy_cmds)(?![A-Za-z0-9 _])}{"\$m->$1"}geo);
    } else {
	while ($buf =~ m{(mc_($easy_cmds)(?![A-Za-z0-9 _]))}go) {
	    $report->($1,"\$m->$2");
	}
    }

    # Boilerplate substitutions for methods with no arguments
    my @subs =
	(['mc_auto_comp',    '$m->fetch_next->path'],
	 ['mc_caller',       '$m->callers(1)->path'],
	 ['mc_comp_source',  '$m->current_comp->source_file'],
	 ['mc_comp_stack',   'map($_->title,$m->callers)'],
	 );
    foreach my $sub (@subs) {
	my ($mc_cmd,$repl) = @$sub;
	if (!$TEST) {
	    $c += ($buf =~ s{$mc_cmd(\s*\(\))?(?!\s*[\(])}{$repl}ge);
	} else {
	    while ($buf =~ m{($mc_cmd(\s*\(\))?(?!\s*[\(]))}g) {
		$report->($1,$repl);
	    }
	}
    }

    # Boilerplate substitutions for methods with arguments
    @subs =
	(['mc_auto_next',    '$m->call_next'],
	 );
    foreach my $sub (@subs) {
	my ($mc_cmd,$repl) = @$sub;
	if (!$TEST) {
	    $c += ($buf =~ s{$mc_cmd}{$repl}ge);
	} else {
	    while ($buf =~ m{($mc_cmd)}g) {
		$report->($1,$repl);
	    }
	}
    }

    # mc_comp_source with simple argument
    if (!$TEST) {
	$c += ($buf =~ s{mc_comp_source\s*\(([^\(\)]+)\)}{"\$m->fetch_comp($1)->source_file"}ge);
    } else {
	while ($buf =~ m{(mc_comp_source\s*\(([^\(\)]+)\))}g) {
	    $report->($1,"\$m->fetch_comp($2)->source_file");
	}
    }

    # mc_suppress_http_header with and without arguments
    if (!$TEST) {
	$c += ($buf =~ s{mc_suppress_http_header\s*(?!\s*\();?}{}g);
	$c += ($buf =~ s{mc_suppress_http_header\s*\([^\(\)]*\)\s*;?}{}g);
    } else {
	while ($buf =~ m{(mc_suppress_http_header\s*(?!\s*\();?)}g) {
	    $report->($1,"");
	}
	while ($buf =~ m{(mc_suppress_http_header\s*\([^\(\)]*\)\s*;?)}g) {
	    $report->($1,"");
	}
    }    
    
    #
    # Convert $REQ to $m
    #
    if (!$TEST) {
	$c += ($buf =~ s{\$REQ(?![A-Za-z0-9_])}{\$m}go);
    } else {
	while ($buf =~ m{(\$REQ(?![A-Za-z0-9_]))}go) {
	    $report->($1,"\$m");
	}
    }
    
    # Report substitutions we can't handle
    foreach my $cmd (qw(mc_comp_source mc_suppress_http_header)) {
	if ($buf =~ m{$cmd\s*\([^\)]*\(}) {
	    $report_failure->("Can't convert $cmd with complex arguments");
	}
    }
    if ($buf =~ m{mc_date}) {
	$report_failure->("Can't convert mc_date");
    }
	
    if ($TEST) {
	if (@changes) {
	    print scalar(@changes)." substitutions in $path:\n";
	    print join("\n",@changes)."\n";
	}
    }
    
    if ($c && !$TEST) {
	print "$c substitutions in $path\n" if !$QUIET;
	my $outfh = new IO::File ">$file";
	if (!$outfh) { warn "cannot write $path: $!"; return }
	$outfh->print($buf);
    }
    
    foreach my $failure (@failures) {
	print "** Warning: $failure; must fix manually\n";
    }

    print "\n" if (($TEST && @changes) || @failures);
}


main();
