package Module::Build::Convert;

use strict;
use warnings; 

use Carp ();
use Cwd ();
use Data::Dumper ();
use ExtUtils::MakeMaker ();
use File::Basename ();
use File::HomeDir ();
use File::Slurp ();
use File::Spec ();
use IO::File ();

our $VERSION = '0.22';

sub new {
    my ($self, %params) = (shift, @_);
    my $obj = bless { Config => { Path             => $params{Path}             || '',
                                  Makefile_PL      => $params{Makefile_PL}      || 'Makefile.PL',
	                          Build_PL         => $params{Build_PL}         || 'Build.PL',
		                  MANIFEST         => $params{MANIFEST}         || 'MANIFEST',
				  RC               => $params{RC}               || '.make2buildrc',
				  Create_RC        => $params{Create_RC}        || 0,
				  Exec_Makefile    => $params{Exec_Makefile}    || 0,
			          Verbose          => $params{Verbose}          || 0,
				  Debug            => $params{Debug}            || 0,
			          Use_Native_Order => $params{Use_Native_Order} || 0,
			          Len_Indent       => $params{Len_Indent}       || 3,
			          DD_Indent        => $params{DD_Indent}        || 2,
	               	          DD_Sortkeys      => $params{DD_Sortkeys}      || 1 }}, $self;
    $obj->{Config}{Makefile_PL} = File::Basename::basename($obj->{Config}{Makefile_PL});
    $obj->{Config}{Build_PL}    = File::Basename::basename($obj->{Config}{Build_PL});
    $obj->{Config}{MANIFEST}    = File::Basename::basename($obj->{Config}{MANIFEST});
    $obj->{Config}{RC}          = File::Spec->catfile(File::HomeDir::home(), $obj->{Config}{RC});
    if ($params{Path}) {
        $obj->{Config}{Makefile_PL} = File::Spec->catfile($params{Path}, $obj->{Config}{Makefile_PL});
        $obj->{Config}{Build_PL}    = File::Spec->catfile($params{Path}, $obj->{Config}{Build_PL});
	$obj->{Config}{MANIFEST}    = File::Spec->catfile($params{Path}, $obj->{Config}{MANIFEST});
    }
    return $obj;
}

sub convert {
    my $self = shift;
    $self->_create_rcfile if $self->{Config}{Create_RC};
    $self->_makefile_ok;
    $self->_do_verbose("Converting $self->{Config}{Makefile_PL} -> $self->{Config}{Build_PL}\n");
    $self->_get_data;
    if ($self->{Config}{Exec_Makefile}) {
        $self->_do_verbose("Executing $self->{Config}{Makefile_PL}\n");
        $self->_run_makefile;
    } else {
        $self->_parse_makefile;
    }
    $self->_convert;
    $self->_dump;
    $self->_write;
    $self->_add_to_manifest if -e $self->{Config}{MANIFEST};
}

sub _create_rcfile {
    my $self = shift;   
    my $rcfile = $self->{Config}{RC};
    if (-e $rcfile && !-z $rcfile && File::Slurp::read_file($rcfile) =~ /\w+/) {
        die "$rcfile exists\n";
    } else {
        my $data = $self->_parse_data('create_rc');
	my $fh = IO::File->new(">$rcfile") or die "Can't open $rcfile: $!\n";
	print $fh $data;
	$fh->close;
	print "Created $rcfile\n";
	exit;
    }
}

sub _makefile_ok {
    my $self = shift;
    my $makefile;
    if (-e $self->{Config}{Makefile_PL}) {
        $makefile = File::Slurp::read_file($self->{Config}{Makefile_PL});
    } else {
        die 'No ', File::Basename::basename($self->{Config}{Makefile_PL}), ' found at ', 
          $self->{Config}{Path} !~ /^\.\// && $self->{Config}{Path} =~ m{[quotemeta([/\])]} 
	    ? File::Basename::dirname($self->{Config}{Makefile_PL}) 
	    : Cwd::cwd(), "\n";
    }
    die "$self->{Config}{Makefile_PL} does not consist of WriteMakefile()\n"
      unless $makefile =~ /WriteMakefile\s*\(/s;
    die "Indirect arguments to WriteMakefile() via hash are not supported\n" 
      if $makefile =~ /WriteMakefile\(\s*%\w+.*\s*\)/s && !$self->{Config}{Exec_Makefile};
}

sub _run_makefile {
    my $self = shift;
    no warnings 'redefine';
    *ExtUtils::MakeMaker::WriteMakefile = sub {
      %{$self->{make_args}} = @{$self->{make_args_arr}} = @_;
    };
    # beware, do '' overwrites existing globals
    $self->_save_globals;
    do $self->{Config}{Makefile_PL};
    $self->_restore_globals;
}

sub _save_globals {
    my $self = shift;
    my @vars;
    my $makefile = File::Slurp::read_file($self->{Config}{Makefile_PL});
    $makefile =~ s/.*WriteMakefile\(\s*?(.*?)\);.*/$1/s;
    while ($makefile =~ s/\$(\w+)//) {
        push @vars, $1 if defined(${$1});
    }
    no strict 'refs';
    for my $var (@vars) {
        ${__PACKAGE__.'::globals'}{$var} = ${$var};
    }
}

sub _restore_globals {
    my $self = shift;
    no strict 'refs';
    while (my ($var, $value) = each %{__PACKAGE__.'::globals'}) {
        ${__PACKAGE__.'::'.$var} = $value;
    }
}    

sub _get_data {
    my $self = shift;
    my @data = $self->_parse_data;
    
    $self->{Data}{table}           = { split /\s+/, shift @data };
    $self->{Data}{default_args}    = { split /\s+/, shift @data };
    $self->{Data}{sort_order}      = [ split /\s+/, shift @data ];
   ($self->{Data}{begin}, 
    $self->{Data}{end})            =                      @data;

    # allow for embedded values such as clean => { FILES => '' }
    foreach my $arg (keys %{$self->{Data}{table}}) {
        if (index($arg, '.') > 0) {
	    my @path = split /\./, $arg;
	    my $value = $self->{Data}{table}->{$arg};
	    my $current = $self->{Data}{table};
	    while (@path) {
	        my $key = shift @path;
		$current->{$key} ||= @path ? {} : $value;
		$current = $current->{$key};
	    }
	}
    }
}

sub _parse_data {
    my $self = shift;
    my $create_rc = 1 if (shift || 'undef') eq 'create_rc';
    my ($data, @data_parsed);
    my $rcfile = $self->{Config}{RC};
    if (-e $rcfile && !-z $rcfile && File::Slurp::read_file($rcfile) =~ /\w+/) {
	$data = File::Slurp::read_file($rcfile);
    } else {
        local $/ = '__END__';
	$data = <DATA>;
	chomp($data);
    }
    unless ($create_rc) {
        @data_parsed = do {               #  # description       
	    split /#\s+.*\s+?-\n/, $data; #  -     
        };
    }
    unless ($create_rc) {
        # superfluosity
        shift @data_parsed;
	chomp($data_parsed[-1]);
	for my $line (split /\n/, $data_parsed[0]) {
	    next unless $line;
	    if ($line =~ /^#/) {
	        my ($arg) = split /\s+/, $line;
	        $self->{disabled}{substr($arg, 1)} = 1;
	    }
	}
        @data_parsed = map { 1 while s/^#.*?\n(.*)$/$1/gs; $_ } @data_parsed;
    }
    return $create_rc ? $data : @data_parsed;
}

sub _parse_makefile {
    my $self = shift;
    my (@histargs, %makeargs);
    my ($makefile, $makecode_begin, $makecode_end) = $self->_read_makefile;
    $self->{make_code}{begin} = $makecode_begin;
    $self->{make_code}{end}   = $makecode_end;
    $self->_debug("Entering parse\n");
    while ($makefile) {
        if ($makefile =~ s/^\s*['"]?(\w+)['"]?\s*=>\s*['"]?([\$\@\%\\\-\w]+.*?)['"]?(?:,?\n|,?(\s+#\s+\w+.*?)\n)//) {
	    my ($arg, $value, $comment) = ($1,$2,$3);
	    $comment ||= '';
	    $value =~ tr/['"]//d;
            $makeargs{$arg} = $value;
	    push @histargs, $arg;
            if (defined($comment) && defined($self->{Data}{table}{$arg})) {
                $self->{make_comments}{$self->{Data}{table}{$arg}} = $comment;
	    }
	    $self->_debug("Found scalar\narg: $arg\nvalue: $value\ncomment: $comment\nremaining args:\n$makefile\n\n");
	} elsif ($makefile =~ s/^\s*['"]?(\w+)['"]?\s*=>\s*\[\s*(.*?)\s*\](?:,?\n|,?(\s+#\s+\w+.*?)\n)//s) {
	    my ($arg, $values, $comment) = ($1,$2,$3);
	    $comment ||= '';
	    $makeargs{$arg} = [ map { tr/['",]//d; $_ } split /,\s*/, $values ];
	    push @histargs, $arg;
	    if (defined($comment) && defined($self->{Data}{table}{$arg})) {
                $self->{make_comments}{$self->{Data}{table}{$arg}} = $comment;
	    }
	    $self->_debug("Found array\narg: $arg\nvalues: $values\ncomment: $comment\nremaining args:\n$makefile\n\n");
	} elsif ($makefile =~ s/^\s*['"]?(\w+)['"]?\s*=>\s*\{\s*(.*?)\s*\}(?:,?\n|,?(\s+#\s+\w+.*?)\n)//s) {
	    my ($arg, $values, $comment) = ($1,$2,$3);
	    $comment ||= '';
	    my @values = split /,\s*/, $values;
	    my @values_clean;
	    for my $value (@values) {
		push @values_clean, map { tr/['",]//d; $_ } split /\s*=>\s*/, $value;
	    }
	    @values = @values_clean;
	    $makeargs{$arg} = { @values };
	    push @histargs, $arg;
            if (defined($comment) && defined($self->{Data}{table}{$arg})) {
                $self->{make_comments}{$self->{Data}{table}{$arg}} = $comment;
	    }
	    $self->_debug("Found hash\narg: $arg\nvalues: $values\ncomment: $comment\nremaining args:\n$makefile\n\n");
	} else {
	    my $makecode;
	    # ? : statements
	    if ($makefile =~ s/^\s+(.*?\:\s+\(.*\)\s*),\n//s) {
		$makecode = $1;
	    # heredocs
            } elsif ($makefile =~ s/^\s*(['"]?\w+['"]?\s*=>\s*<<['"]?(.*?)['"]?,.*\2)//s) {
                $makecode = $1;
	    # variables
	    } elsif ($makefile =~ s/^\s*([\Q$@%\E]\w+)\s*//) {
                $makecode = $1;
	    # commented	
            } elsif ($makefile =~ s/^\s*(#.*?)\n//) {
	        $makecode = $1;
	    } else {
                $makefile =~ s/^\s+(.*?)[,]\s*//;
                $makecode = $1;
            }
	    SUBST: for my $make (keys %{$self->{Data}{table}}) {
		if ($makecode =~ /\b$make\b/s) {
		    $makecode =~ s/$make/$self->{Data}{table}{$make}/;
		    last SUBST;
		}
            }
	    pop @histargs until $self->{Data}{table}{$histargs[-1]};
	    push @{$self->{make_code}{$self->{Data}{table}{$histargs[-1]}}}, $makecode;
	    $self->_debug("Found code\ncode: $makecode\nremaining args:\n$makefile\n\n");
	}
    }
    $self->_debug("Leaving parse\n");
    %{$self->{make_args}} = %makeargs;
}

sub _read_makefile {
    my $self = shift;
    my $makefile = File::Slurp::read_file($self->{Config}{Makefile_PL});
    $makefile =~ s/(.*)\&?WriteMakefile\(\s*?(.*?)\);(.*)/$2/s;
    my $makecode_begin = $1;
    my $makecode_end   = $3;
    $makecode_begin =~ s/\s*([#\w]+.*)\s*/$1/s;
    $makecode_end   =~ s/\s*([#\w]+.*)\s*/$1/s;
    return ($makefile, $makecode_begin, $makecode_end);
}

sub _convert {
    my $self = shift;                        
    $self->_insert_args; 
    for my $arg (keys %{$self->{make_args}}) {
        if ($self->{disabled}{$arg}) {
	    $self->_do_verbose("*** $arg disabled, skipping\n");
	    next;
	}
        unless ($self->{Data}{table}->{$arg}) {
	    $self->_do_verbose("*** $arg unknown, skipping\n");
	    next;
	}     
        if (ref $self->{make_args}{$arg} eq 'HASH') {                                
	    if (ref $self->{Data}{table}->{$arg} eq 'HASH') {
		# embedded structure
		my @iterators = ();
		my $current = $self->{Data}{table}->{$arg};
		my $value = $self->{make_args}{$arg};
		push @iterators, _iterator($current, $value, keys %$current);
		while (@iterators) {
		    my $iterator = shift @iterators;
		    while (($current, $value) = $iterator->()) {
			if (ref $current eq 'HASH') {
			    push @iterators, _iterator($current, $value, keys %$current);
			} else {
			    if (substr($current, 0, 1) eq '@') {
				my $attr = substr($current, 1);
			        if (ref $value eq 'ARRAY') {
				    push @{$self->{build_args}}, { $attr => $value };
				} else {
				    push @{$self->{build_args}}, { $attr => [ split ' ', $value ] };
				}
			    } else {
			        push @{$self->{build_args}}, { $current => $value };
			    }
			}
		    }
		}
	    } else {
		# flat structure
		my %tmphash;
		%{$tmphash{$self->{Data}{table}->{$arg}}} = 
		  map { $_ => $self->{make_args}{$arg}{$_} } keys %{$self->{make_args}{$arg}};  
		push @{$self->{build_args}}, \%tmphash;
	    }
	} elsif (ref $self->{make_args}{$arg} eq 'ARRAY') {                         
	    push @{$self->{build_args}}, { $self->{Data}{table}->{$arg} => $self->{make_args}{$arg} };
	} elsif (ref $self->{make_args}{$arg} eq '') {
	    push @{$self->{build_args}}, { $self->{Data}{table}->{$arg} => $self->{make_args}{$arg} };
	} else { # unknown type
	    warn "$arg - unknown type of argument\n";
	}
    }
    $self->_sort_args if @{$self->{Data}{sort_order}};
}

sub _insert_args {
    my ($self, $make) = @_;
    my @insert_args;
    my %build = map { $self->{Data}{table}{$_} => $_ } keys %{$self->{Data}{table}};
    while (my ($arg, $value) = each %{$self->{Data}{default_args}}) {
        no warnings 'uninitialized';
        if (exists $self->{make_args}{$build{$arg}}) {
	    $self->_do_verbose("*** Overriding default \'$arg => $value\'\n");
	    next;
	}
        $value = {} if $value eq 'HASH';
	$value = [] if $value eq 'ARRAY';
	$value = '' if $value eq 'SCALAR' && $value !~ /\d+/;
	push @insert_args, { $arg => $value };
    }
    @{$self->{build_args}} = @insert_args;
}

sub _iterator {
    my ($build, $make) = (shift, shift);
    my @queue = @_;
    return sub {
        my $key = shift @queue || return;
	return $build->{$key}, $make->{$key};
    }
}

sub _sort_args {
    my $self = shift;
    my %native_sortorder;
    if ($self->{Config}{Use_Native_Order}) {
	no warnings 'uninitialized';
        for (my ($i,$s) = 0; $s < @{$self->{make_args_arr}}; $s++) {
	    next unless $s % 2 == 0;
	    $native_sortorder{$self->{Data}{table}{$self->{make_args_arr}[$s]}} = $i
	      if exists $self->{Data}{table}{$self->{make_args_arr}[$s]};
	    $i++;
	}
    } 
    my %sortorder;
    {
        my %have_args = map { keys %$_ => 1 } @{$self->{build_args}};
	# Filter sort items, that we didn't receive as args,
	# and map the rest to according array indexes.
	my $i = 0;
	if ($self->{Config}{Use_Native_Order}) {
	    my %slot;
	    for my $arg (grep $have_args{$_}, @{$self->{Data}{sort_order}}) {
	        if ($native_sortorder{$arg}) {
	            $sortorder{$arg} = $native_sortorder{$arg};
		    $slot{$native_sortorder{$arg}} = 1;
	        } else {
	            $i++ while $slot{$i};
		    $sortorder{$arg} = $i++;
	        }      
            }
	    my @args = sort { $sortorder{$a} <=> $sortorder{$b} } keys %sortorder;
            $i = 0; %sortorder = map { $_ => $i++ } @args;
	} else {
	    %sortorder = map { 
	      $_ => $i++ 
            } grep $have_args{$_}, @{$self->{Data}{sort_order}};
	}
    }
    my ($is_sorted, @unsorted);
    do {
        $is_sorted = 1;
          SORT: for (my $i = 0; $i < @{$self->{build_args}}; $i++) {   
              my ($arg) = keys %{$self->{build_args}[$i]};
	      unless (exists $sortorder{$arg}) {
	          push @unsorted, splice(@{$self->{build_args}}, $i, 1);
	          next;
	      }
              if ($i != $sortorder{$arg}) {
                  $is_sorted = 0;
                  # Move element $i to pos $sortorder{$arg}
		  # and the element at $sortorder{$arg} to
		  # the end. 
	          push @{$self->{build_args}}, 
		    splice(@{$self->{build_args}}, $sortorder{$arg}, 1,    
		      splice(@{$self->{build_args}}, $i, 1));
                  last SORT;    
	      }
          }
    } until ($is_sorted);
    push @{$self->{build_args}}, @unsorted;  
}

sub _dump {
    my $self = shift;
    $Data::Dumper::Indent    = $self->{Config}{DD_Indent} || 2;
    $Data::Dumper::Quotekeys = 0;
    $Data::Dumper::Sortkeys  = $self->{Config}{DD_Sortkeys};
    $Data::Dumper::Terse     = 1;
    my $d = Data::Dumper->new(\@{$self->{build_args}});
    $self->{buildargs_dumped} = [ $d->Dump ];
}

sub _write { 
    my $self = shift;
    $self->{INDENT} = ' ' x $self->{Config}{Len_Indent};
    no warnings 'once';
    my $fh = IO::File->new(">$self->{Config}{Build_PL}") 
      or die "Can't open $self->{Config}{Build_PL}: $!\n";
    my $selold = select($fh);
    $self->_compose_header;
    $self->_write_begin;
    $self->_write_args;
    $self->_write_end;
    $fh->close;
    select($selold);
    $self->_do_verbose("Conversion done\n");
}

sub _compose_header {
    my $self = shift;
    my ($insert_comments, $insert_statements);
    my $note = '# Note: this file has been initially generated by '.__PACKAGE__." $VERSION";
    if (defined($self->{make_code}{begin})) {
        $self->_do_verbose("Removing ExtUtils::MakeMaker as dependency\n");
        $self->{make_code}{begin} =~ s/[ ]*(?:require|use)\s+ExtUtils::MakeMaker\s*;//;
        $insert_comments ||= '';
        while ($self->{make_code}{begin} =~ s/^(#[!]?.*?\n)//) {
            $insert_comments .= $1;
        }
	chomp($insert_comments);
        $insert_statements ||= '';
        while ($self->{make_code}{begin} =~ /(?:require|use)\s+.*?;/) {
            $self->{make_code}{begin} =~ s/^\n?(.*?;)//s;
	    $insert_statements .= "$1\n";
        }
	chomp($insert_statements);
	1 while $self->{make_code}{begin} =~ s/^\n//;
	chomp($self->{make_code}{begin}) while $self->{make_code}{begin} =~ /\n$/s;
    }
    $self->{Data}{begin} = $insert_comments || $insert_statements
      ? ($insert_comments  =~ /\w/ ? "$insert_comments\n" : '') . "$note\n" . 
        ($insert_statements =~ /\w/ ? "\n$insert_statements\n\n" : "\n") .
        $self->{Data}{begin}
      : "$note\n\n" . $self->{Data}{begin};
}

sub _write_begin {
    my $self = shift;  
    my $INDENT = substr($self->{INDENT}, 0, length($self->{INDENT}) - 1);
    $self->_subst_makecode('begin');
    $self->{Data}{begin} =~ s/(\$INDENT)/$1/eego;
    $self->_do_verbose(File::Basename::basename($self->{Config}{Build_PL}), " written:\n", 2);
    $self->_do_verbose($self->{Data}{begin}, 2);
    print $self->{Data}{begin};
}

sub _write_args {
    my $self = shift;
    my $arg;
    my $regex = '$chunk =~ /=> \{/';
    for my $chunk (@{$self->{buildargs_dumped}}) {
        # Hash/Array output                       
        if ($chunk =~ /=> [\{\[]/) {
	    # Remove redundant parentheses
	    $chunk =~ s/^\{.*?\n(.*(?{eval $regex ? '\}' : '\]'}))\s+\}\s+$/$1/os;
	    Carp::croak $@ if $@;
	    # One element per each line
	    my @lines;        
            push @lines, $1 while $chunk =~ s/^(.*?\n)(.*)$/$2/s;         
	    # Gather whitespace up to hash key in order
	    # to recreate native Dump() indentation.
	    my ($whitespace) = $lines[0] =~ /^(\s+)(\w+)/;
	    $arg = $2;
	    my $shorten = length($whitespace);
            for (my $i = 0; $i < @lines; $i++) {
	        my $line = $lines[$i];
	        chomp($line);
		# Remove additional whitespace
	        $line =~ s/^\s{$shorten}(.*)$/$1/o;
		# Quote sub hash keys
		$line =~ s/^(\s+)([\w:]+)/$1'$2'/ if $line =~ /^\s+/;
		# Add comma where appropriate (version numbers, parentheses)          
	        $line .= ',' if $line =~ /[\d+\}\]]$/;
		$line =~ s/'(\d|\$\w+)'/$1/g;
		my $output = "$self->{INDENT}$line";
		$output .= ($i == $#lines && defined($self->{make_comments}{$arg}))
		  ? "$self->{make_comments}{$arg}\n" : "\n"; 
		$self->_do_verbose($output, 2);
		print $output;
            }
	} else { # Scalar output                                                 
	    chomp($chunk);
	    # Remove redundant parentheses
            $chunk =~ s/^\{\s+(.*?)\s+\}$/$1/s;
	    $chunk =~ s/'(\d|\$\w+)'/$1/g;
	    ($arg) = $chunk =~ /^\s*(\w+)/;
	    my $output = "$self->{INDENT}$chunk,";
	    $output .= $self->{make_comments}{$arg} if defined($self->{make_comments}{$arg});
	    $self->_do_verbose("$output\n", 2);
	    print "$output\n";
	}
	no warnings 'uninitialized';
	if ($self->{make_code}{$arg}) {
	    for my $line (@{$self->{make_code}{$arg}}) {
	        $line .= ',' unless $line =~ /^#/;
    	        $self->_do_verbose("$self->{INDENT}$line\n", 2);
	        print "$self->{INDENT}$line\n";
	    }
	}
    }
}

sub _write_end {
    my $self = shift;
    my $INDENT = substr($self->{INDENT}, 0, length($self->{INDENT}) - 1);
    $self->_subst_makecode('end');
    $self->{Data}{end} =~ s/(\$INDENT)/$1/eego;
    $self->_do_verbose($self->{Data}{end}, 2);
    print $self->{Data}{end};
}

sub _subst_makecode {
    my ($self, $section) = @_;
    $self->{make_code}{$section} ||= '';
    $self->{make_code}{$section} =~ /\w/
      ? $self->{Data}{$section} =~ s/\$MAKECODE/$self->{make_code}{$section}/o
      : $self->{Data}{$section} =~ s/\n\$MAKECODE\n//o;
}    

sub _add_to_manifest {
    my $self = shift;
    my $fh = IO::File->new("<$self->{Config}{MANIFEST}") 
      or die "Can't open $self->{Config}{MANIFEST}: $!\n";
    my @manifest = <$fh>;
    $fh->close;
    my $build_pl = File::Basename::basename($self->{Config}{Build_PL});
    unless (grep { $_ =~ /^$build_pl\s+$/o } @manifest) {
        unshift @manifest, "$build_pl\n";
        $fh = IO::File->new(">$self->{Config}{MANIFEST}") 
	  or die "Can't open $self->{Config}{MANIFEST}: $!\n";
        print $fh sort @manifest;
        $fh->close;
	$self->_do_verbose("Added to $self->{Config}{MANIFEST}: $self->{Config}{Build_PL}\n");
    }
}

sub _do_verbose {
    my $self = shift;
    my $level = $_[-1] =~ /^\d$/ ? pop : 1; 
    if (($self->{Config}{Verbose} && $level == 1) 
      || ($self->{Config}{Verbose} == 2 && $level == 2)) {
        print STDOUT @_;
    }
}

sub _debug {
    my $self = shift;
    warn @_ if $self->{Config}{Debug};
}

1;
__DATA__
 
# argument conversion 
-
NAME                  module_name
DISTNAME              dist_name
ABSTRACT              dist_abstract
AUTHOR                dist_author
VERSION               dist_version
VERSION_FROM          dist_version_from
PREREQ_PM             requires
PL_FILES              PL_files
PM                    pm_files
MAN1PODS              pod_files
XS                    xs_files
INC                   include_dirs
INSTALLDIRS           installdirs
DESTDIR               destdir
CCFLAGS               extra_compiler_flags
EXTRA_META            meta_add
SIGN                  sign
LICENSE               license
clean.FILES           @add_to_cleanup

# default arguments 
-
#build_requires       HASH
#recommends	      HASH
#conflicts	      HASH
license               unknown
create_readme         1
create_makefile_pl    passthrough

# sorting order 
-
module_name
dist_name
dist_abstract
dist_author
dist_version
dist_version_from
requires
build_requires
recommends
conflicts
PL_files
pm_files
pod_files
xs_files
include_dirs
installdirs
destdir
add_to_cleanup
extra_compiler_flags
meta_add
sign
license
create_readme
create_makefile_pl

# begin code 
-
use Module::Build;

$MAKECODE

my $build = Module::Build->new
$INDENT(
# end code 
-
$INDENT);
  
$build->create_build_script;

$MAKECODE

__END__

=head1 NAME

Module::Build::Convert - Makefile.PL to Build.PL converter

=head1 SYNOPSIS

 require Module::Build::Convert; 

 my %params = (Path => '/path/to/perl/distribution',
               Verbose => 2,
	       Use_Native_Order => 1,
               Len_Indent => 4);

 my $make = Module::Build::Convert->new(%params);                            
 $make->convert;

=head1 DESCRIPTION

C<ExtUtils::MakeMaker> has been a de-facto standard for the common distribution of Perl
modules; C<Module::Build> is expected to supersede C<ExtUtils::MakeMaker> in some time
(part of the Perl core as of 5.10?)

The transition takes place slowly, as the converting process manually achieved 
is yet an uncommon practice. The Module::Build::Convert F<Makefile.PL> parser is 
intended to ease the transition process.

=head1 CONSTRUCTOR

=head2 new

Options:

=over 4

=item Path

Path to a Perl distribution. Default: C<''>

=item Makefile_PL

Filename of the Makefile script. Default: F<Makefile.PL>

=item Build_PL

Filename of the Build script. Default: F<Build.PL>

=item MANIFEST

Filename of the MANIFEST file. Default: F<MANIFEST>

=item RC

Filename of the RC file. Default: F<.make2buildrc>

=item Create_RC

Create a RC file in the homedir of the current user.
Default: 0

=item Exec_Makefile

Execute the Makefile.PL via C<'do Makefile.PL'>.
Default: 0

=item Verbose

Verbose mode. If set to 1, overridden defaults and skipped arguments
are printed while converting; if set to 2, output of C<Verbose = 1> and
created Build script will be printed. May be set via the make2build 
switches C<-v> (mode 1) and C<-vv> (mode 2). Default: 0

=item Debug

Rudimentary debug facility for examining the parsing process.
Default: 0

=item Use_Native_Order

Native sorting order. If set to 1, the native sorting order of
the Makefile arguments will be tried to preserve; it's equal to
using the make2build switch C<-n>. Default: 0

=item Len_Indent

Indentation (character width). May be set via the make2build
switch C<-l>. Default: 3

=item DD_Indent

C<Data::Dumper> indendation mode. Mode 0 will be disregarded in favor
of 2. Default: 2

=item DD_Sortkeys

C<Data::Dumper> sort keys. Default: 1

=back

=head1 METHODS

=head2 convert

Parses the F<Makefile.PL>'s C<WriteMakefile()> arguments and converts them
to C<Module::Build> equivalents; subsequently the according F<Build.PL>
is created. Takes no arguments.

=head1 DATA SECTION

=head2 Argument conversion

C<ExtUtils::MakeMaker> arguments followed by their C<Module::Build> equivalents. 
Converted data structures preserve their native structure,
that is, C<HASH> -> C<HASH>, etc.

 NAME                  module_name
 DISTNAME              dist_name
 ABSTRACT              dist_abstract
 AUTHOR                dist_author
 VERSION               dist_version
 VERSION_FROM          dist_version_from
 PREREQ_PM             requires
 PL_FILES              PL_files
 PM                    pm_files
 MAN1PODS              pod_files
 XS                    xs_files
 INC                   include_dirs
 INSTALLDIRS           installdirs
 DESTDIR               destdir
 CCFLAGS               extra_compiler_flags
 EXTRA_META            meta_add
 SIGN                  sign
 LICENSE               license
 clean.FILES           @add_to_cleanup

=head2 Default arguments

C<Module::Build> default arguments may be specified as key/value pairs. 
Arguments attached to multidimensional structures are unsupported.

 #build_requires       HASH
 #recommends	       HASH
 #conflicts	       HASH
 license               unknown
 create_readme         1
 create_makefile_pl    passthrough

Value may be either a string or of type C<SCALAR, ARRAY, HASH>.

=head2 Sorting order

C<Module::Build> arguments are sorted as enlisted herein. Additional arguments, 
that don't occur herein, are lower prioritized and will be inserted in 
unsorted order after preceedingly sorted arguments.

 module_name
 dist_name
 dist_abstract
 dist_author
 dist_version
 dist_version_from
 requires
 build_requires
 recommends
 conflicts
 PL_files
 pm_files
 pod_files
 xs_files
 include_dirs
 installdirs
 destdir
 add_to_cleanup
 extra_compiler_flags
 meta_add
 sign
 license
 create_readme
 create_makefile_pl

=head2 Begin code

Code that preceeds converted C<Module::Build> arguments.

 use Module::Build;

 $MAKECODE

 my $b = Module::Build->new
 $INDENT(

=head2 End code

Code that follows converted C<Module::Build> arguments.

 $INDENT);

 $b->create_build_script;

 $MAKECODE

=head1 INTERNALS

=head2 co-opting C<WriteMakefile()>

This behavior is no longer the default way to receive WriteMakefile()'s
arguments; the Makefile.PL is now statically parsed unless one forces
manually the co-opting of WriteMakefile().

In order to convert arguments, a typeglob from C<WriteMakefile()> to an 
internal sub will be set; subsequently Makefile.PL will be executed and the
arguments are then accessible to the internal sub.

=head2 Data::Dumper

Converted C<ExtUtils::MakeMaker> arguments will be dumped by 
C<Data::Dumper's> C<Dump()> and are then furtherly processed.

=head1 BUGS & CAVEATS

C<Module::Build::Convert> should be considered experimental as the parsing 
of the Makefile.PL doesn't necessarily return valid arguments, especially for
Makefiles with bad or even worse, missing intendation.

The parsing process may sometimes hang with or without warnings in such cases.
Debugging by using the appropriate option/switch (see CONSTRUCTOR/new) may reveal
the root cause.

=head1 SEE ALSO

L<http://www.makemaker.org>, L<ExtUtils::MakeMaker>, L<Module::Build>, 
L<http://www.makemaker.org/wiki/index.cgi?ModuleBuildConversionGuide>

=head1 AUTHOR

Steven Schubiger <schubiger@cpan.org>

=head1 LICENSE

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

See L<http://www.perl.com/perl/misc/Artistic.html>	    

=cut
