App-git-ship

 view release on metacpan or  search on metacpan

lib/App/git/ship/perl.pm  view on Meta::CPAN

package App::git::ship::perl;
use Mojo::Base 'App::git::ship';

use Module::CPANfile;
use Mojo::File qw(path tempfile);
use Mojo::Util 'decode';
use POSIX qw(setlocale strftime LC_TIME);
use Pod::Markdown;

use constant DEBUG => $ENV{GIT_SHIP_DEBUG} || 0;

my $CONTRIB_END_RE        = qr{^=head1};
my $CONTRIB_NAME_EMAIL_RE = qr{^(\w[\w\s]*\w) - C<(.+)>$};
my $CONTRIB_NAME_RE       = qr{^(\w[\w\s]*\w)$};
my $CONTRIB_START_RE      = qr{^=head1 AUTHOR};
my $VERSION_RE            = qr{\W*\b(\d+\.[\d_]+)\b};

sub build {
  my $self = shift;

  $self->clean(0);
  $self->system(prove => split /\s/, $self->config('build_test_options'))
    if $self->config('build_test_options');
  $self->clean(0);
  $self->run_hook('before_build');
  $self->_render_makefile_pl if -e 'cpanfile';
  $self->_timestamp_to_changes;
  $self->_update_version_info;
  $self->_render_readme;
  $self->_make('manifest');
  $self->_make('dist', '-e');
  $self->run_hook('after_build');
  $self;
}

sub can_handle_project {
  my ($class, $file) = @_;
  return $file =~ /\.pm$/ ? 1 : 0 if $file;
  return path('lib')->list_tree->grep(sub {/\.pm$/})->size;
}

sub clean {
  my $self  = shift;
  my $all   = shift // 1;
  my @files = qw(Makefile Makefile.old MANIFEST MYMETA.json MYMETA.yml);

  unlink 'Makefile' and $self->_make('clean') if -e 'Makefile';

  push @files, qw(Changes.bak META.json META.yml) if $all;
  push @files, $self->_dist_files->each;

  for my $file (@files) {
    next unless -e $file;
    unlink $file or warn "!! rm $file: $!" and next;
    say "\$ rm $file" unless $self->SILENT;
  }

  return $self;
}

sub ship {
  my $self      = shift;
  my $dist_file = $self->_dist_files->[0];
  my $changelog = $self->config('changelog_filename');
  my $uploader;

  require CPAN::Uploader;

lib/App/git/ship/perl.pm  view on Meta::CPAN


  @lines = ("#!start included $file\n");
  local @ARGV = ($file);
  push @lines, $_ while <>;
  return join "", @lines, "#!end included $file\n";
}

sub _make {
  my ($self, @args) = @_;

  $self->_render_makefile_pl unless -e 'Makefile.PL';
  $self->system(perl => 'Makefile.PL') unless -e 'Makefile';
  $self->system(make => @args);
}

sub _render_makefile_pl {
  my $self    = shift;
  my $prereqs = Module::CPANfile->load->prereqs;
  my $args    = {force => 1};
  my $r;

  $args->{PREREQ_PM}      = $prereqs->requirements_for(qw(runtime requires))->as_string_hash;
  $r                      = $prereqs->requirements_for(qw(build requires))->as_string_hash;
  $args->{BUILD_REQUIRES} = $r;
  $r                      = $prereqs->requirements_for(qw(test requires))->as_string_hash;
  $args->{TEST_REQUIRES}  = $r;
  $args->{RECOMMENDS}     = $prereqs->requirements_for(qw(runtime recommends))->as_string_hash;
  $args->{CONTRIBUTORS}   = [split /,\s*/, $self->config('contributors')];

  $self->render_template('Makefile.PL', $args);
  $self->system(qw(perl -c Makefile.PL));    # test Makefile.PL
}

sub _render_readme {
  my $self = shift;
  my $skip;

  if (-e 'README.md') {
    my $re = "# NAME[\\n\\r\\s]+@{[$self->config('project_name')]}\\s-\\s";
    $skip = path('README.md')->slurp =~ m!$re! ? undef : 'Custom README.md is in place';
  }
  elsif (my @alternative = path->list->grep(sub {/^README/i})->each) {
    $skip = "@alternative exists.";
  }

  if ($skip) {
    say "# Will not generate README.md: $skip" unless $self->SILENT;
    return;
  }

  open my $README, '>:encoding(UTF-8)', 'README.md' or die "Write README.md: $!";
  my $parser = Pod::Markdown->new;
  $parser->output_fh($README);
  $parser->parse_string_document(path($self->config('main_module_path'))->slurp);
  say '# Generated README.md' unless $self->SILENT;
}

sub _timestamp_to_changes {
  my $self      = shift;
  my $changelog = $self->config('changelog_filename');
  my $loc       = setlocale(LC_TIME);
  my $release_line;

  $release_line = sub {
    my $v   = shift;
    my $str = $self->config('new_version_format');
    $str =~ s!(%-?\d*)v!{ sprintf "${1}s", $v }!e;
    setlocale LC_TIME, 'C';
    $str = strftime $str, localtime;
    setlocale LC_TIME, $loc;
    return $str;
  };

  local @ARGV = $changelog;
  local $^I   = '';
  while (<>) {
    $self->config(next_version => $1)
      if s/^$VERSION_RE\x20*(?:Not Released)?\x20*([\r\n]+)/{ $release_line->($1) . $2 }/e;
    print;    # print back to same file
  }

  say '# Building version ', $self->config('next_version') unless $self->SILENT;
  $self->abort('Unable to add timestamp to ./%s', $changelog) unless $self->config('next_version');
}

sub _update_changes {
  my $self = shift;

  unless (eval "require CPAN::Changes; 1") {
    say "# Cannot update './Changes' without CPAN::Changes. Install using 'cpanm CPAN::Changes'."
      unless $self->SILENT;
    return;
  }

  my $changes = CPAN::Changes->load('Changes');
  $changes->preamble(
    'Revision history for perl distribution ' . ($self->config('project_name') =~ s!::!-!gr));
  path('Changes')->spurt($changes->serialize);
  say "# Generated Changes" unless $self->SILENT;
}

sub _update_version_info {
  my $self    = shift;
  my $version = $self->config('next_version')
    or $self->abort('Internal error: Are you sure Changes has a timestamp?');

  local @ARGV = ($self->config('main_module_path'));
  local $^I   = '';
  my %r;
  while (<>) {
    $r{pod} ||= s/$VERSION_RE/$version/ if /^=head1 VERSION/ .. $r{pod} && /^=(cut|head1)/ || eof;
    $r{var} ||= s/((?:our)?\s*\$VERSION)\s*=.*/$1 = '$version';/;
    print;    # print back to same file
  }

  $self->abort('Could not update VERSION in %s', $self->config('main_module_path')) unless $r{var};
}

1;

=encoding utf8

=head1 NAME

App::git::ship::perl - Ship your Perl module

=head1 SYNOPSIS

  # Set up basic files for a Perl repo
  # (Not needed if you already have an existing repo)



( run in 2.589 seconds using v1.01-cache-2.11-cpan-ceb78f64989 )