App-prepare4release
view release on metacpan or search on metacpan
lib/App/prepare4release.pm view on Meta::CPAN
sub run {
my $readme = 'README.md';
open my $fh, '<:encoding(UTF-8)', $readme
or die "Cannot open README.md: $!";
local $/;
my $text = <$fh> // '';
close $fh;
$text = _strip_readme_badge_markdown_block($text);
my $inner = badge_block();
my $block = $inner . "\n\n";
my $new_text = _insert_readme_badges_after_regen( $text, $block );
return if $new_text eq $text;
open my $out, '>:encoding(UTF-8)', $readme
or die "Cannot write README.md: $!";
print {$out} $new_text;
close $out;
return;
}
run() if !caller;
1;
P4R_INJECT_TMPL
}
sub _render_inject_readme_badges_pl {
my ( $class, $inner ) = @_;
$inner = '' unless defined $inner;
my $marker = '__P4R_BADGE_INNER_BODY__';
croak "inject-readme-badges.pl: badge text must not contain $marker"
if index( $inner, $marker ) >= 0;
my $t = $class->_inject_readme_badges_pl_template;
my $i = index( $t, $marker );
croak 'inject-readme-badges.pl: template missing inner placeholder'
if $i < 0;
substr( $t, $i, length($marker), $inner );
return $t;
}
sub write_inject_readme_badges_script {
my ( $class, $cwd, $inner, $verbose ) = @_;
my $rel = $class->inject_readme_badges_relpath;
my @segs = split qr{/}, $rel;
my $file = pop @segs;
my $path = File::Spec->catfile( $cwd, @segs, $file );
make_path( File::Spec->catfile( $cwd, @segs ) ) if @segs;
my $text = $class->_render_inject_readme_badges_pl($inner);
open my $out, '>:encoding(UTF-8)', $path
or croak "Cannot write '$path': $!";
print {$out} $text;
close $out;
chmod 0755, $path or warn "[prepare4release] chmod 0755 '$path': $!\n";
warn "[prepare4release] wrote $rel (standalone README badge injector)\n"
if $verbose;
return;
}
# Makefile fragment: pod2* then inject README shields via generated script (no App::pm dep).
sub _postamble_block {
my ( $class, $opts ) = @_;
$opts = {} unless ref $opts eq 'HASH';
my $tab = "\t";
my $want_pod2github = $opts->{github} || $opts->{gitlab};
my $pod_cmd = $want_pod2github ? 'pod2github' : 'pod2markdown';
my $inj = $class->inject_readme_badges_relpath;
return <<"EOF";
# BEGIN PREPARE4RELEASE_POSTAMBLE
sub MY::postamble {
return '' if !-e '.git';
<<'PREPARE4RELEASE_POD2README';
pure_all :: README.md
README.md : \$(VERSION_FROM)
${tab}$pod_cmd \$< \$@
${tab}\$(PERL) $inj
PREPARE4RELEASE_POD2README
}
# END PREPARE4RELEASE_POSTAMBLE
EOF
}
sub makefile_has_pod2github {
my ( $class, $content ) = @_;
return $content =~ /pod2github\b/;
}
sub makefile_has_pod2markdown {
my ( $class, $content ) = @_;
return $content =~ /pod2markdown\b/;
}
sub _replace_marked_postamble_block {
my ( $class, $content, $new_block ) = @_;
return $content
unless $content =~ /^\# BEGIN PREPARE4RELEASE_POSTAMBLE/m;
my $out = $content;
# Line endings: require \r?\n so CRLF files (common on Windows / some editors)
# still match; a strict \n-only pattern leaves the block unchanged and pod2*
# never updates.
# No /x: it would strip spaces in "# BEGIN ..." and treat # as comments. The pattern
# must be a single line: multiline s{}{} would include literal \n\t from this file.
$out =~ s/^\# BEGIN PREPARE4RELEASE_POSTAMBLE\s*\r?\n.*?^\# END PREPARE4RELEASE_POSTAMBLE\s*\r?\n?/$new_block/ms;
return $out;
}
sub _replace_legacy_my_postamble_heredoc {
my ( $class, $content, $new_block ) = @_;
my $re = qr/
^sub \s+ MY::postamble \s* \{
[\s\S]*?
<<'(?:POD2README|PREPARE4RELEASE_POD2README)'
[\s\S]*?
^(?:POD2README|PREPARE4RELEASE_POD2README)\s*$
\}
/mx;
my $out = $content;
return $out unless $out =~ $re;
lib/App/prepare4release.pm view on Meta::CPAN
F<Makefile> exists, otherwise C<pod2github> or C<pod2markdown>), then injects
Markdown shield lines (C<[](link)>) into F<README.md> after the first
title block (runs of C<#> headings) or before C<# NAME> when that is the first
heading. The F<Makefile.PL> postamble runs F<maint/inject-readme-badges.pl> after
C<pod2github>/C<pod2markdown> so badges stay in sync without a runtime dependency
on this distribution. Strips any
legacy badge block from POD after C<__END__>. License and minimum Perl badges
are always added; with C<--cpan>, also Repology, CPAN version, and cpants. The
GitHub Actions CI badge is added only with C<--github>; the GitLab pipeline badge
only with C<--gitlab> (host from C<git.server>, else from C<git.repo> URL, else
C<gitlab.com>). License shield (always blue) uses the same key as ExtUtils::MakeMaker
(F<Makefile.PL> C<LICENSE>), or the type inferred from a root F<LICENSE> file when
present; the link is the repository F<LICENSE> blob when that file exists and
C<--github> or C<--gitlab> is set (branch from C<git.default_branch>, default
C<main>), otherwise the usual canonical license URL. Minimum Perl on the shield
comes from C<min_perl_version> / C<perl_min> in the JSON file, else
F<Makefile.PL> C<MIN_PERL_VERSION>, else the stricter of makefile and main module
(as for CI).
=item *
Creates author tests under F<xt/author/> when missing: C<pod.t> (L<Test::Pod>),
C<eol.t> (L<Test::EOL>), C<pod-coverage.t> (L<Test::Pod::Coverage>), using
L<Test2::V1>.
=item *
With C<--cpan>, after the steps above: ensures F<LICENSE> exists. The license
I<type> is taken from F<Makefile.PL> C<LICENSE> (via the same snippet scan as
elsewhere in this tool); if that is missing, I<perl> (same terms as Perl 5) is
assumed. The file text is downloaded from official upstream sources (for
C<perl>, the F<Artistic> and F<Copying> files from the Perl 5 repository; for
C<apache_2>, C<mit>, C<gpl_3>, etc., the canonical license URLs). If a fetch
fails, a short built-in fallback is written. If F<README> is missing but
F<README.md> exists, writes a short stub F<README> pointing readers to
F<README.md>. Creates a default F<MANIFEST.SKIP> when none is present (skipping
F<blib/>, F<cover_db/>, F<nytprof/>, tarballs, F<.git/>, etc.); runs
C<perl Makefile.PL>, copies F<MYMETA.*> to F<META.*>, and C<make manifest> so
F<MANIFEST> matches the tree for CPAN packaging.
=item *
Warns when any F<t/*.t> or F<xt/**/*.t> file starts with C<use Test::More> or
C<use Test::Most> (legacy assertion frameworks). Prefer L<Test2::V1> or
L<Test2::Tools::Spec>.
=item *
Scans F<lib/>, F<bin/>, F<maint/>, F<t/>, and optionally F<xt/> for C<use> /
C<require> and compares with C<PREREQ_PM> / C<TEST_REQUIRES> in F<Makefile.PL>.
Core modules for the target minimum Perl are skipped unless a minimum module
version is given on the C<use> line (see L<Module::CoreList>). By default only a
warning is printed; C<--sync-deps> or C<dependencies.sync> in
F<prepare4release.json> updates F<Makefile.PL> and appends to F<cpanfile> when
present. C<dependencies.skip> disables the check.
=back
=head1 README badge injector (F<maint/inject-readme-badges.pl>)
The C<MY::postamble> fragment cannot hold large, self-contained Perl I<sub>s:
C<ExtUtils::MakeMaker> expects that section to expand into Makefile rules, and
keeping badge logic only in F<Makefile.PL> would either duplicate a lot of text
or imply loading this distribution at C<make README.md> time. Instead,
C<prepare4release> writes F<maint/inject-readme-badges.pl>, a small, generated
program (core modules only) that strips prior shield lines and inserts the
frozen Markdown block computed on the last run (same flags as C<--github> /
C<--gitlab> / C<--cpan>). Downstream distributions should I<commit> that file
with the rest of the tree so C<make README.md> works in a clean clone and the
file is included in the CPAN tarball like any other tracked asset. Re-run
C<prepare4release> after changing repository URLs, license, or badge-related
options so the script and F<README.md> stay consistent. No runtime dependency on
C<App::prepare4release> is added to the target module.
=head1 CONFIGURATION FILE
File name: F<prepare4release.json> (in the distribution root).
An empty file or whitespace-only file is treated as an empty JSON object C<{}>.
Invalid JSON logs a warning and is treated as C<{}>.
=over 4
=item C<author>
Optional. Copied into F<Makefile.PL> C<AUTHOR> (distinct from C<git.author>).
=item C<abstract>
Optional. Copied into F<Makefile.PL> C<ABSTRACT>.
=item C<abstract_from>
Optional. Copied into F<Makefile.PL> C<ABSTRACT_FROM>.
=item C<license>
Optional. Copied into F<Makefile.PL> C<LICENSE>.
=item C<exe_files>
Optional. JSON array of paths; copied into F<Makefile.PL> C<EXE_FILES>.
=item C<module_name>
Optional. Perl package (e.g. C<My::Module>). If omitted, taken from the
C<VERSION_FROM> module's C<package> line, from C<NAME> in F<Makefile.PL>, or from
the first C<lib/**/*.pm> file. If set, also written to F<Makefile.PL> C<NAME>.
=item C<name>
Optional. Alternative to C<module_name> for F<Makefile.PL> C<NAME> when
C<module_name> is absent.
=item C<version_from>
Optional. Path written to F<Makefile.PL> C<VERSION_FROM> when set.
=item C<version>
Optional. If omitted, taken from C<$VERSION> in the resolved main module file.
( run in 0.926 second using v1.01-cache-2.11-cpan-364913b4093 )