The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl
package Su::Build::CustomBuild;

use strict;
use warnings;
use Fatal qw(open);
use Data::Dumper;
use File::Copy;
use base 'Module::Build';
use Config;
our $VERSION = "0.4";

=pod

=head1 NAME

CustomBuild - The custom build class.

=head1 SYNOPSYS

=head1 DESCRIPTION

The custom build class.

=head1 ACTIONS

=over

=cut

=item ACTION_list

List up the custom actions.

=cut

sub ACTION_list {

  print "Action entries:\n";
  my $pkg_name = __PACKAGE__;

  $pkg_name =~ s/::/\//g;
  for my $path (@INC) {
    next if ( !-e $path . '/' . $pkg_name . ".pm" );
    open( my $f, "<", $path . '/' . $pkg_name . ".pm" );
    for (<$f>) {
      if ( $_ =~ /sub ACTION_([a-zA-Z0-9_]+?)\s*{/ ) {
        print $1 . "\n";
      }
    }
  } ## end for my $path (@INC)

} ## end sub ACTION_list

sub ACTION_add_changes {

  # Get the latest tag version from `git tag` command.
  open my $IN, "git tag|";
  my @tag = <$IN>;
  @tag = sort { $b <=> $a } @tag;
  my $tag_version = $tag[0] || '';
  $tag_version =~ s/(\r|\n)//g if $tag_version;

##  print @tag;

  # Get the latest tag version from the Changes file.
  my $changes_f       = "Changes";
  my $changes_version = '';
  open( my $F, '<', $changes_f );
  while ( my $line = <$F> ) {

    $line =~ s/(\r|\n)//g;

    ## print "[trace] line:$line\n";

    #    if ( $line =~ /^(\d+\.\d+)$/ ) { print "match:" . $1 . "\n" }
    if ( $line =~ /^(\d+\.\d+)$/ ) {
      $changes_version = $1;
      last;
    }
  } ## end while ( my $line = <$F> )
  close $F;

  # Get the source version via ShipIt.
  my $source_ver;
  {
    require ShipIt;
    my $SHIP_CONF_FILE = ".shipit";
    my $conf           = ShipIt::Conf->parse($SHIP_CONF_FILE);
    my $state          = ShipIt::State->new($conf);
    $source_ver = $state->pt->current_version;
  }

  print "changes_version:$changes_version\n";
  print "tag_version:$tag_version\n";
  print "source_version:$source_ver\n";

  die
"[ERROR]The version number in the projcet source and git tag is the same. Increment source version."
    if ( $tag_version && $source_ver == $tag_version );

# Note that if both has the same verion, it's ok. Because repository may has some not tagged changes.
  die
"[ERROR]The version number in the Changes is larger than the tag in repostory. So nothing to add to the Changes."
    if ( $tag_version && $changes_version > $tag_version );

  die
"[ERROR]The tag version in Changes not exist in the git tags. Changes mey be already updated by you manually."
    if ( @tag && $changes_version && not grep /$changes_version/, @tag );

  # Get the log from `git log` command to add to the Changes file.
  # Make sure the date format is iso.

  my @log = $changes_version
    ? `git log --date=iso $changes_version..HEAD`

    # If the tag is not exist, then collect all logs.
    : `git log --date=iso`;

  my @to_add;
  for my $line (@log) {
    if ( $line =~ /^(commit|Author:)/ ) {    # Ignore commit id and Author.
    } elsif ( $line =~ /^\n$/ ) {            # Ignore empty line.
    } else {
      push @to_add, $line;
    }
  } ## end for my $line (@log)

  ## print Dumper(@to_add);

  die "[ERROR]log contents are empty." unless @to_add;

  move( 'Changes', 'Changes.bak' );

  open( my $Changes_fh, '>', 'Changes' );
  print $Changes_fh "$source_ver\n";
  print $Changes_fh join( "", @to_add );
  close $Changes_fh;

  # Add old entries to the new Changes.
  `echo "" >> Changes`;
  `cat Changes.bak >> Changes`;

} ## end sub ACTION_add_changes

sub ACTION_refresh_modules {

  # Not implemented.
}

sub ACTION_stage {
  my $self = shift;

  # Not implemented.
}

=begin comment

This method is called instead of constructor.

=end comment

=cut

sub resume {
  my $self = shift;
  no warnings qw(once);
  no strict 'refs';
  no warnings 'redefine';

  # *CustomBuild::ACTION_hoge = sub {
  #   print "action hoge\n";
  # };

  my $obj = $self->SUPER::resume;

  #  print Dumper( $obj->{args} );
  #  print Dumper( $obj->{config} );
  #  print Dumper( $obj->{properties} );

  # Get defined test_types.
  my $tests = $obj->{properties}->{test_types};

  # Register the test hander.
  foreach my $key ( sort keys %{$tests} ) {

    *{ __PACKAGE__ . "::ACTION_test${key}" } = sub {
      shift->generic_test( type => "${key}" );
    };
  } ## end foreach my $key ( sort keys...)

  # Register the shipit scenarios..
  foreach my $ship_file ( glob '.shipit_*' ) {
    require ShipIt;
    my $kind = $ship_file;
    $kind =~ s/\.shipit_(.+)/$1/;

    *{ __PACKAGE__ . "::ACTION_shipit_${kind}" } = sub {

      my $SHIP_CONF_FILE = $ship_file;
      print "ship file:" . $SHIP_CONF_FILE . "\n";
      my $conf  = ShipIt::Conf->parse($SHIP_CONF_FILE);
      my $state = ShipIt::State->new($conf);
      foreach my $step ( $conf->steps ) {
        warn "Running step $step\n";
        $step->run($state);
      }

    };
  } ## end foreach my $ship_file ( glob...)

  return $obj;
} ## end sub resume

sub ACTION_check_pod {
  my $self  = shift;
  my @files = `find lib -type f -name "*.pm"`;

  #print Dumper(@files);

  for my $elm (@files) {
    chomp $elm;

    #    print $elm . "\n";
    open my $f, '<', $elm;
    my $txt = join '', <$f>;

    #    print $txt;

    # Check missing empty line before a section.
    while ( $txt =~ /[^\n]\n(=head\d|=item\s*\w*|=cut|=over|=back|=over)/g ) {

      my $show_txt   = $` . $&;
      my @show_lines = split "\n", $show_txt;
      my $line_num   = scalar @show_lines;
      @show_lines = @show_lines[ $#show_lines - 10 ... $#show_lines ];

      #      @show_lines = grep { $_ } @show_lines;
      print "\n[ERROR]no empty line:at $elm L$line_num\n"
        . join( "\n", @show_lines )
        . "          <---------\n";
    } ## end while ( $txt =~ ...)

    while (
      $txt =~ /\n(=head\d|=item\s*\w*|=cut|=over|=back|=over)[^\n]*?\n[^\n]/g )
    {

      my $show_txt   = $` . $&;
      my $all_txt    = $` . $& . $';
      my @show_lines = split "\n", $show_txt;
      my @all_lines  = split "\n", $all_txt;
      my $line_num   = scalar @show_lines;
      @show_lines =
        grep { defined $_ }
        @all_lines[ $#show_lines - 10 ... $#show_lines + 5 ];

      #      @show_lines = grep { $_ } @show_lines;
      print "\n[ERROR]no empty line:before $elm L$line_num\n"

        #        . join( "\n", @show_lines )
        . join(
        "\n",
        (
          @show_lines[ 0 .. $#show_lines - 6 ],
          $show_lines[ $#show_lines - 5 ] . "          <---------\n",
          @show_lines[ $#show_lines - 4 .. $#show_lines ],
          "\n"
        )
        );
    } ## end while ( $txt =~ ...)

    # Check excessive empty lines before a section.
    while ( $txt =~ /\n{3,}(=head\d)/g ) {
      my $show_txt   = $` . $&;
      my @show_lines = split "\n", $show_txt;
      my $line_num   = scalar @show_lines;
      @show_lines = @show_lines[ $#show_lines - 10 ... $#show_lines ];

      #      @show_lines = grep { $_ } @show_lines;
      print "\n[ERROR]more than 2 empty line::at $elm L$line_num\n"
        . join( "\n", @show_lines )
        . "          <---------\n";
    } ## end while ( $txt =~ /\n{3,}(=head\d)/g)

    # Check whether empty line is exit before the source section.
    my $prev_line = '';
    while (
      $txt =~

/\n(?:=head\d*[a-zA-Z() ]*|=item[\s\w_($)]*(?=\n))(.+?)(?:=cut|=over|=back|=over)/sg

 # /\n(?:=head\d*[a-zA-Z() ]*|=item[\s\w_()]*)(.+?)(?:=cut|=over|=back|=over)/sg
      )
    {

      # print "begin\n";
      my @lines = split( /\n/, $1 );
      my $b_code_block = 0;
      my $line_num;
      for my $line (@lines) {
        ++$line_num;
        $line =~ tr/\r//;

        # print "match:$elm\n";
        # print "line:$line:block:$b_code_block\n";

        if ( !$b_code_block && $line =~ /^\s.+/ && $prev_line ne '' ) {
          print "\n[ERROR] not empty line before source part.$elm L$line_num\n";
          print "$prev_line\n$line\n";
          $b_code_block = 1;
        } elsif ( !$b_code_block && $line =~ /^\s.+/ ) {
          $b_code_block = 1;
        } elsif ( $b_code_block
          && $line =~ /^[^\s].+/ )

          # && $prev_line =~ /^\s.+/ )
        {
          $b_code_block = 0;
        } ## end elsif ( $b_code_block && ...)

        $prev_line = $line;
      } ## end for my $line (@lines)
          # print "end\n";
    } ## end while ( $txt =~...)

  } ## end for my $elm (@files)

} ## end sub ACTION_check_pod

sub ACTION_uninstall {
  my $self        = shift;
  my $module_path = $self->{properties}->{module_name};
  $module_path =~ s!::!/!g;
  my $packlist_file =
    $Config{sitearchexp} . "/auto/" . $module_path . "/.packlist";

  die ".packlist file not found. $packlist_file" unless -e $packlist_file;

  open( my $F, '<', $packlist_file );
  while ( my $line = <$F> ) {

    $line =~ s/(\r|\n)//g;
    print "[INFO]Deleting: " . $line . "\n";
    unlink $line or die "[ERROR]Can not delete file:" . $line . ":$!";

  } ## end while ( my $line = <$F> )

  print "[INFO]Deleting: " . $packlist_file . "\n";
  unlink $packlist_file
    or die "[ERROR]Can not delete file:" . $packlist_file . ":$!";

} ## end sub ACTION_uninstall

# Just list up the install files.
sub ACTION_fakeuninstall {
  my $self        = shift;
  my $module_path = $self->{properties}->{module_name};
  $module_path =~ s!::!/!g;
  my $packlist_file =
    $Config{sitearchexp} . "/auto/" . $module_path . "/.packlist";

  die ".packlist file not found. $packlist_file" unless -e $packlist_file;

  open( my $F, '<', $packlist_file );
  print join( '', <$F> );

} ## end sub ACTION_fakeuninstall

sub ACTION_ms_test {
  my $self = shift;

  # The temporaly directory to extract tar.gz file for test.
  my $work_dir = 'c:/tmp';

  # my $win_perl = 'c:/perl/bin/perl.exe';

  # Make the path of perl executable file from the PATH entry.
  my $win_perl =
`set|grep -e ^PATH=|perl -ne 'print join("\n",split ":" ,\$_);'|grep -i perl/bin|perl -ne '\$_=~s!/cygdrive/([a-z])(.*)!\$1:\$2/perl.exe!;print \$_;'`;
  chomp $win_perl;
  print "ms perl path:" . $win_perl;
  die "perl.exe not exist: $win_perl:" unless ( -f $win_perl );

# `set|grep -e ^PATH=|perl -ne 'print join("\n",split ":" ,$_);'|grep -i perl/bin|perl -ne '$_=~s!/cygdrive/([a-z])(.*)!$1:$2/perl.exe!;print $_;'`;

  import Data::Dumper;
  import File::Spec;
  print Dumper( $self->{properties}->{module_name} );
  my $fname = $self->{properties}->{module_name};
  $fname =~ s/::/-/;

  my @f = glob("Su-*\.tar\.gz");
  unless (@f) {
    die "Dist file not exist. Place the tar.gz file to the current directory.";
  }
  print "target file:" . $f[0] . "\n";

  # Remove old work file.
  my $work_file = File::Spec->catfile( $work_dir, $f[0] );
  if ( -f $work_file ) {
    `rm $work_file`;
  }

  # Copy to the work dir.
  `cp $f[0] $work_dir`;

  die "target file not exist: $f[0]:" unless ( -f $f[0] );

  my $file_mod_ts = ( stat $f[0] )[9];
  my $file_cur_ts = time;
  print "[TRACE]file_mod_ts:$file_mod_ts\n";
  print "[TRACE]file_cur_ts:$file_cur_ts\n";

  die "target file is too old which is build over 30 min before."
    if $file_mod_ts < $file_cur_ts - 60 * 30;

  my $dir_name = $f[0];
  $dir_name =~ s/(Su-[0-9.]+)\.tar\.gz/$1/;
  print "dir_name:" . $dir_name . "\n";
  my $uncompress_dir = File::Spec->catfile( $work_dir, $dir_name );

  # Remove old work uncompressed dir.
  if ( -d $uncompress_dir ) {
    `rm -fr $uncompress_dir`;
  }

  unless ( -f $work_file ) {
    die "work file not exist.";
  }
  my $cyg_work_file = $work_file;
  my $cyg_work_dir  = $work_dir;
  $cyg_work_file =~ s/://;
  $cyg_work_dir  =~ s/://;
  print "file:" . $cyg_work_file . "\n";
  `tar -C /cygdrive/$cyg_work_dir -xvzf /cygdrive/$cyg_work_file`;

  $uncompress_dir =~ s/://;
  print "changing directory:" . $uncompress_dir . "\n";
  print `cd /cygdrive/$uncompress_dir;$win_perl Build.PL`;
  print `cd /cygdrive/$uncompress_dir;$win_perl Build test`;

  # perl -e ' print `cd c:/tmp/Su-0.008;c:/perl/bin/perl.exe Build.PL`'
  # perl -e ' print `cd c:/tmp/Su-0.008;c:/perl/bin/perl.exe Build help`'
  # perl -e ' print `cd c:/tmp/Su-0.008;c:/perl/bin/perl.exe Build test`'

} ## end sub ACTION_ms_test

1;