package Dist::Zilla::PluginBundle::Author::AMON;
use strict;
use warnings;
use utf8;

## no critic
our $VERSION = '0.000001';
$VERSION = eval $VERSION;
## use critic

use Moose;
with 'Dist::Zilla::Role::PluginBundle::Easy';

# configure_meta
use Dist::Zilla::Plugin::GithubMeta;
# configure_prereqs
use Dist::Zilla::Plugin::Prereqs::AuthorDeps;
# configure_gather
use Dist::Zilla::Plugin::Git::GatherDir;
use Dist::Zilla::Plugin::PruneCruft;
# configure_extra_files
use Dist::Zilla::Plugin::CPANFile;
use Dist::Zilla::Plugin::MetaYAML;
use Dist::Zilla::Plugin::MetaJSON;
use Dist::Zilla::Plugin::MetaProvides::Package;
use Dist::Zilla::Plugin::Manifest;
use Dist::Zilla::Plugin::License;
use Dist::Zilla::Plugin::Readme;
use Dist::Zilla::Plugin::PPPort;
# configure_extra_tests
use Dist::Zilla::Plugin::Test::Perl::Critic;
use Dist::Zilla::Plugin::PodSyntaxTests;
use Dist::Zilla::Plugin::PodCoverageTests;
use Dist::Zilla::Plugin::Test::Kwalitee::Extra;
use Dist::Zilla::Plugin::RunExtraTests;
# configure_post_build
use Dist::Zilla::Plugin::CopyFilesFromBuild;
use Dist::Zilla::Plugin::ReadmeAnyFromPod;
# configure_pre_release
use Dist::Zilla::Plugin::Git::CheckFor::CorrectBranch;
use Dist::Zilla::Plugin::Git::Check;
use Dist::Zilla::Plugin::CheckChangesHasContent;
use Dist::Zilla::Plugin::CheckVersionIncrement;
# configure_release
use Dist::Zilla::Plugin::TestRelease;
use Dist::Zilla::Plugin::ConfirmRelease;
use Dist::Zilla::Plugin::UploadToCPAN;
# configure_post_release
use Dist::Zilla::Plugin::NextRelease;
use Dist::Zilla::Plugin::Git::Commit;
use Dist::Zilla::Plugin::Git::Tag;
use Dist::Zilla::Plugin::Git::Push;

=encoding UTF-8

=head1 NAME

Dist::Zilla::PluginBundle::Author::AMON - dzil config choices by AMON

=head1 SYNOPSIS

    # in dist.ini
    [@Author::AMON]
    github = example-user/example-repo
    autogenerate_file = Build.PL
    ; github_issues = 1
    ; exclude_author_deps = inc::Example
    ; gather_exclude_file = TODO.txt
    ; gather_exclude_match = ^scripts/
    ; ppport = src/ppport.h

=head1 DESCRIPTION

This module contains a starting point for AMON's dist.ini.
There are a couple of more or less sensible defaults
(like pointing metadata at Github, or only gathering files tracked by Git)
and a number of valuable checks so that I mess up fewer CPAN releases
(like checking that the Changes file was updated.)
There are also a few convenience features
(like not gathering files that will be generated by the build process.)

Decisions not currently made by this module:

Which build system to use (e.g. Module::Build vs. MakeMaker).

How the version number is managed
(e.g. taken from the module with [VersionFromMainModule]).

How prereqs are found. Simply using [AutoPrereqs] is likely to be correct.

All this may change in a future release without prior notice,
and will not be seen as a backwards-incompatible change.

=head1 CONFIGURATION

=cut

sub mvp_multivalue_args {
    return qw/
        exclude_author_deps
        autogenerate_file
        gather_exclude_file
        gather_exclude_match
    /;
}

=head2 github

The name of a GitHub repository in the format C<< <user>/<repo> >>.
This is used to set the repo and user fields in
L<[GithubMeta]|Dist::Zilla::Plugin::GithubMeta>.

Required string.

=cut

has github => (
    is => 'ro',
    isa => 'Str',
    lazy => 1,
    default => sub {
        return delete shift->payload->{github}
            // die q(Github repo required);
    },
);

=head2 github_issues

Whether Github Issues should be used as the bugtracker.
If not, bugtracker info needs to be added manually.

Optional boolean. Defaults to true.

=cut

has github_issues => (
    is => 'ro',
    isa => 'Bool',
    lazy => 1,
    default => sub { delete shift->payload->{github_issues} // 1 },
);

=head2 exclude_author_deps

Exclude authordeps prereqs.
This module will add your "dzil authordeps" via
L<[Prereqs::AuthorDeps]|Dist::Zilla::Plugin::Prereqs::AuthorDeps>.
List any modules that should not be required,
e.g. because they are bundled as "inc/" modules.

Optional string. Can be specified multiple times. Defaults to empty list.

=cut

has exclude_author_deps => (
    is => 'ro',
    isa => 'ArrayRef[Str]',
    lazy => 1,
    default => sub { delete shift->payload->{exclude_author_deps} // [] },
);

=head2 gather_exclude_file

Filenames that should not be gathered.
Use L<gather_exclude_match|/gather_exclude_match>
to exclude all files matching a pattern, e.g. a whole directory.
Use L<autogenerate_file|/autogenerate_file>
to exclude files that will be copied from the built dist.

Optional string. Can be specified multiple times. Defaults to empty list.

=cut

has gather_exclude_file => (
    is => 'ro',
    isa => 'ArrayRef[Str]',
    lazy => 1,
    default => sub { delete shift->payload->{gather_exclude_file} // [] },
);

=head2 gather_exclude_match

A regex to match filenames that should not be gathered.
Use L<gather_exclude_file|/gather_exclude_file>
to exclude single files.
Use L<autogenerate_file|/autogenerate_file>
to exclude files that will be copied from the built dist.

Optional string. Can be specified multiple times. Defaults to empty list.

=cut

has gather_exclude_match => (
    is => 'ro',
    isa => 'ArrayRef[Str]',
    lazy => 1,
    default => sub { delete shift->payload->{gather_exclude_match} // [] },
);

=head2 autogenerate_file

Files that should be copied from the built dist.
These won't be gathered.
After the build phase, they will be copied into your source tree.
Use L<gather_exclude_file|/gather_exclude_file>
or L<gather_exclude_match|/gather_exclude_match>
to exclude files, without copying them from the built dist.

The following files will be copied or autogenerated
without having to add them to this configuration option:

=over

=item *

C<LICENSE> as generated by the [License] plugin.

=item *

C<cpanfile> as generated by the [CPANFile] plugin.

=item *

C<README.md> as generated by the [ReadmeAnyFromPod] plugin.

=item *

C<ppport.h> if specified (see the L<ppport|/ppport> option).

=back

Optional string. Can be specified multiple times. Defaults to empty list.

=cut

my @always_autogenerate = qw(
    LICENSE cpanfile README.md
);

has autogenerate_file => (
    is => 'ro',
    isa => 'ArrayRef[Str]',
    lazy => 1,
    default => sub { delete shift->payload->{autogenerate_file} // [] },
);

=head2 ppport

Add a ppport.h file under the specified path.
The value of this option is the path/filename
where the ppport.h should be placed.
E.g.:

    ...
    ppport = src/ppport.h

Optional string or undef. Defaults to undef.

=cut

has ppport => (
    is => 'ro',
    isa => 'Maybe[Str]',
    lazy => 1,
    default => sub { delete shift->payload->{ppport} },
);

=head1 PLUGINS

The plugins are configured in thematic groups.
Each group is a method that can be overridden in subclasses of this bundle.

=over

=item *

L<configure_meta|/configure_meta>

=item *

L<configure_prereqs|/configure_prereqs>

=item *

L<configure_gather|/configure_gather>

=item *

L<configure_extra_files|/configure_extra_files>

=item *

L<configure_extra_tests|/configure_extra_tests>

=item *

L<configure_post_build|/configure_post_build>

=item *

L<configure_pre_release|/configure_pre_release>

=item *

(configure_release)

A minimum release workflow is hardcoded:

    [TestRelease]
    [ConfirmRelease]
    [UploadToCPAN]

=item *

L<configure_post_release|/configure_post_release>

=back

=cut

sub configure {
    my ($self) = @_;

    $self->configure_meta();
    $self->configure_prereqs();
    $self->configure_gather();
    $self->configure_extra_files();
    $self->configure_extra_tests();
    $self->configure_post_build();
    $self->configure_pre_release();
    $self->add_plugins(qw(
        TestRelease
        ConfirmRelease
        UploadToCPAN
    ));
    $self->configure_post_release();

    return;
}

=head2 configure_meta

Add metadata.

    [GithubMeta]
    user = {{github.user}}
    repo = {{github.repo}}
    issues = {{github_issues}}

Plugins:
L<GithubMeta|Dist::Zilla::Plugin::GithubMeta>.

Configuration options:
L<github|/github>,
L<github_issues|/github_issues>.

=cut

sub configure_meta {
    my ($self) = @_;

    my ($gh_user, $gh_repo) = split m(/), scalar $self->github, 2;
    unless (length $gh_user and length $gh_repo) {
        die "Github repo of the form <user>/<repo> required!";
    }

    $self->add_plugins(
        [GithubMeta => {
            repo => $gh_repo,
            user => $gh_user,
            issues => $self->github_issues }],
    );

    return;
}

=head2 configure_prereqs

Specify prerequisites.

    [Prereqs::AuthorDeps]
    ; exclude = {{exclude_author_deps}}

Plugins:
L<Prereqs::AuthorDeps|Dist::Zilla::Plugin::Prereqs::AuthorDeps>.

Configuration options:
L<exclude_author_deps|/exclude_author_deps>.

=cut

sub configure_prereqs {
    my ($self) = @_;

    my @exclude_author_deps;
    push @exclude_author_deps, @{ $self->exclude_author_deps };

    $self->add_plugins(
        ['Prereqs::AuthorDeps' => {
            (exclude => [ @exclude_author_deps ]) x!! @exclude_author_deps }],
    );

    return;
}

=head2 configure_gather

Gather files to be included in the dist.

    [Git::GatherDir]
    ; exclude_match = {{gather_exclude_match}}
    ; exclude_filename = {{gather_exclude_file}}
    ; exclude_filename = {{autogenerate_file}}
    ; exclude_filename = {{ppport}}
    [PruneCruft]

Plugins:
L<Git::GatherDir|Dist::Zilla::Plugin::Git::GatherDir>,
L<PruneCruft|Dist::Zilla::Plugin::PruneCruft>.

Configuration options:
L<gather_exclude_file|/gather_exclude_file>,
L<gather_exclude_match|/gather_exclude_match>,
L<autogenerate_file|/autogenerate_file>,
L<ppport|/ppport>.

=cut

sub configure_gather {
    my ($self) = @_;

    my @exclude_file;
    push @exclude_file, @always_autogenerate;
    push @exclude_file, @{ $self->autogenerate_file };
    push @exclude_file, @{ $self->gather_exclude_file };
    push @exclude_file, $self->ppport if $self->ppport;

    my @exclude_match;
    push @exclude_match, @{ $self->gather_exclude_match };

    $self->add_plugins(
        ['Git::GatherDir' => {
            (exclude_filename   => [ @exclude_file  ]) x!! @exclude_file,
            (exclude_match      => [ @exclude_match ]) x!! @exclude_match }],
        'PruneCruft',
    );

    return;
}

=head2 configure_extra_files

Autogenerate certain files in the dist.

    ; [NextRelease]
    [CPANFile]
    [MetaYAML]
    [MetaJSON]
    [MetaProvides::Package]
    [Manifest]
    [License]
    [Readme]
    ; [PPPort]
    ; filename = {{ppport}}

Plugins:
L<[CPANFile]|Dist::Zilla::Plugin::CPANFile>,
L<[MetaYAML]|Dist::Zilla::Plugin::MetaYAML>,
L<[MetaJSON]|Dist::Zilla::Plugin::MetaJSON>,
L<[MetaProvides::Package]|Dist::Zilla::Plugin::MetaProvides::Package>,
L<[Manifest]|Dist::Zilla::Plugin::Manifest>,
L<[License]|Dist::Zilla::Plugin::License>,
L<[Readme]|Dist::Zilla::Plugin::Readme>,
L<[PPPort]|Dist::Zilla::Plugin::PPPort>.

Configuration options:
L<ppport|/ppport>.

=cut

sub configure_extra_files {
    my ($self) = @_;

    # also NextRelease, but that must be specified later
    $self->add_plugins(qw(
        CPANFile
        MetaYAML
        MetaJSON
        MetaProvides::Package
        Manifest
        License
        Readme
    ));

    if (my $ppport = $self->ppport) {
        $self->add_plugins(
            ['PPPort' => {
                filename => $ppport }],
        );
    }

    return;
}

=head2 configure_extra_tests

Add extra tests, particularly author tests focussed on release quality.

    [Test::Perl::Critic]
    [PodSyntaxTests]
    [PodCoverageTests]
    [Test::Kwalitee::Extra]
    [RunExtraTests]

Plugins:
L<[Test::Perl::Critic]|Dist::Zilla::Plugin::Test::Perl::Critic>,
L<[PodSyntaxTests]|Dist::Zilla::Plugin::PodSyntaxTests>,
L<[PodCoverageTests]|Dist::Zilla::Plugin::PodCoverageTests>,
L<[Test::Kwalitee::Extra]|Dist::Zilla::Plugin::Test::Kwalitee::Extra>,
L<[RunExtraTests]|Dist::Zilla::Plugin::RunExtraTests>.

=cut

sub configure_extra_tests {
    my ($self) = @_;

    $self->add_plugins(qw(
        Test::Perl::Critic
        PodSyntaxTests
        PodCoverageTests
        Test::Kwalitee::Extra
        RunExtraTests
    ));

    return;
}

=head2 configure_post_build

Post-build actions, such as copying generated files into the source tree.

    [CopyFilesFromBuild
    copy = {{autogenerate_file}}
    ; copy = {{ppport}}
    [ReadmeAnyFromPod]
    type = markdown
    filename = README.md
    location = root
    phase = build

Plugins:
L<[CopyFilesFromBuild]|Dist::Zilla::Plugin::CopyFilesFromBuild>,
L<[ReadmeAnyFromPod]|Dist::Zilla::Plugin::ReadmeAnyFromPod]>.

Configuration options:
L<autogenerate_file|/autogenerate_file>,
L<ppport|/ppport>.

=cut

sub configure_post_build {
    my ($self) = @_;

    # Copy Generated Files

    my @copy_from_build;
    push @copy_from_build, grep { $_ ne 'README.md' } @always_autogenerate;
    push @copy_from_build, @{ $self->autogenerate_file };
    push @copy_from_build, $self->ppport if $self->ppport;

    $self->add_plugins(
        [CopyFilesFromBuild => {
            copy => [ @copy_from_build ] }],
    );

    # Generate markdown README from main module
    $self->add_plugins(
        [ReadmeAnyFromPod => {
            type => 'markdown',
            filename => 'README.md',
            location => 'root',
            phase => 'build' }],
    );

    return;
}

=head2 configure_pre_release

Run pre-release checks.

    [Git::CheckFor::CorrectBranch]
    [Git::Check]
    [CheckChangesHasContent]
    [CheckVersionIncrement]

Plugins:
L<[Git::CheckFor::CorrectBranch]|Dist::Zilla::Plugin::Git::CheckFor::CorrectBranch>,
L<[Git::Check]|Dist::Zilla::Plugin::Git::Check>,
L<[CheckChangesHasContent]|Dist::Zilla::Plugin::CheckChangesHasContent>,
L<[CheckVersionIncrement]|Dist::Zilla::Plugin::CheckVersionIncrement>.

=cut

sub configure_pre_release {
    my ($self) = @_;

    $self->add_plugins(qw(
        Git::CheckFor::CorrectBranch
        Git::Check
        CheckChangesHasContent
        CheckVersionIncrement
    ));

    return;
}

=head2 configure_post_release

Perform post-release bookkeeping.

    [NextRelease]
    time_zone = UTC
    [Git::Commit]
    commit_msg  = release-%v
    [Git::Tag]
    tag_format  = release-%v
    tag_message = release-%v
    [Git::Push]

Plugins:
L<[NextRelease]|Dist::Zilla::Plugin::NextRelease>,
L<[Git::Commit]|Dist::Zilla::Plugin::Git::Commit>,
L<[Git::Tag]|Dist::Zilla::Plugin::Git::Tag>,
L<[Git::Push]|Dist::Zilla::Plugin::Git::Push>.

=cut

sub configure_post_release {
    my ($self) = @_;

    my $tag_format = 'release-%v';

    $self->add_plugins(
        [NextRelease => {
            time_zone => 'UTC' }],
        ['Git::Commit' => {
            commit_msg => $tag_format }],
        ['Git::Tag' => {
            tag_format  => $tag_format,
            tag_message => $tag_format }],
        'Git::Push',
    );

    return;
}

=head1 SUPPORT

Homepage:
L<https://github.com/latk/p5-Dist-Zilla-PluginBundle-Author-AMON>

Bugtracker:
L<https://github.com/latk/p5-Dist-Zilla-PluginBundle-Author-AMON/issues>

=head1 AUTHOR

amon - Lukas Atkinson (cpan: AMON) <amon@cpan.org>

=head1 COPYRIGHT

Copyright 2017 Lukas Atkinson

This library is free software and may be distributed under the same terms as perl itself. See L<http://dev.perl.org/licenses/>.

=begin Pod::Coverage

    mvp_multivalue_args
    configure

=end Pod::Coverage

=cut

__PACKAGE__->meta->make_immutable;

1;
