#   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
#   file: lib/Dist/Zilla/PluginBundle/Author/VDB.pm

#pod =encoding UTF-8
#pod
#pod =head1 COPYRIGHT AND LICENSE
#pod
#pod Copyright © 2015 Van de Bugger
#pod
#pod This file is part of perl-Dist-Zilla-PluginBundle-Author-VDB.
#pod
#pod perl-Dist-Zilla-PluginBundle-Author-VDB is free software: you can redistribute it and/or modify
#pod it under the terms of the GNU General Public License as published by the Free Software
#pod Foundation, either version 3 of the License, or (at your option) any later version.
#pod
#pod perl-Dist-Zilla-PluginBundle-Author-VDB is distributed in the hope that it will be useful, but
#pod WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
#pod PARTICULAR PURPOSE. See the GNU General Public License for more details.
#pod
#pod You should have received a copy of the GNU General Public License along with
#pod perl-Dist-Zilla-PluginBundle-Author-VDB. If not, see <http://www.gnu.org/licenses/>.
#pod
#pod =cut

#   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

#pod =head1 DESCRIPTION
#pod
#pod It is unlikely that someone else will want to use it, so I will not bother with documenting it, at
#pod least for now.
#pod
#pod =for Pod::Coverage configure
#pod
#pod =cut

package Dist::Zilla::PluginBundle::Author::VDB;

use Moose;
use namespace::autoclean;

# PODNAME: Dist::Zilla::PluginBundle::Author::VDB
# ABSTRACT: VDB's plugin bundle
our $VERSION = 'v0.8.2'; # VERSION

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

#   These modules used by the bundle directly.
use Carp qw{ croak };
use Dist::Zilla::File::InMemory;
use Dist::Zilla::File::OnDisk;
use Path::Tiny;
use Sub::Exporter::ForMethods qw{ method_installer };
use Data::Section { installer => method_installer }, -setup;

#   These modules are used by hooks. Require all the modules explicitly now to avoid unexpected
#   failures in the middle of build or release.
use File::chdir         ();
use IPC::Run3           ();
use IPC::System::Simple ();
use Path::Tiny 0.070    ();
use Perl::Version       ();

#   Implicitly required module.
use Pod::Weaver::PluginBundle::Author::VDB ();

# --------------------------------------------------------------------------------------------------

#pod =option minimum_perl
#pod
#pod Optional, default value C<5.006>.
#pod
#pod =cut

has minimum_perl => (
    is          => 'ro',
    isa         => 'Str',
    lazy        => 1,
    default     => sub {
        my ( $self ) = @_;
        return $self->payload->{ minimum_perl } // '5.006';
    },
);

# --------------------------------------------------------------------------------------------------

#pod =option copying
#pod
#pod Name of POD file to generate distribution F<COPYING> text file. Empty value disables generation
#pod F<COPYING> file.
#pod
#pod C<Str>, optional, default value C<doc/copying.pod>.
#pod
#pod =cut

has copying => (
    is          => 'ro',
    isa         => 'Str',
    lazy        => 1,
    default     => sub {
        my ( $self ) = @_;
        return $self->payload->{ copying } // 'doc/copying.pod';
    },
);

# --------------------------------------------------------------------------------------------------

#pod =option readme
#pod
#pod Names of POD files to generate distribution F<README> text file. This is a multi-value option.
#pod Empty values are ignored. Empty list disables generating F<README>file.
#pod
#pod C<ArrayRef[Str]>, optional, default value C<[ 'doc/what.pod', 'doc/why.pod', 'doc/naming.pod', 'doc/forms.pod', 'doc/source.pod', 'doc/distribution.pod', 'doc/installing.pod', 'doc/hacking.pod', 'doc/documentation.pod', 'doc/feedback.pod', 'doc/glossary.pod' ]>.
#pod
#pod =cut

has readme => (
    is          => 'ro',
    isa         => 'Maybe[ArrayRef[Str]]',
    lazy        => 1,
    default     => sub {
        my ( $self ) = @_;
        my $readme = $self->payload->{ readme } // [ 'doc/what.pod', 'doc/why.pod', 'doc/naming.pod', 'doc/forms.pod', 'doc/source.pod', 'doc/distribution.pod', 'doc/installing.pod', 'doc/hacking.pod', 'doc/documentation.pod', 'doc/feedback.pod', 'doc/glossary.pod' ];
        $readme = [ grep( { $_ ne  '' } @$readme ) ];   # Ignore empty items.
        if ( not @$readme ) {
            $readme = undef;
        };
        return $readme;
    },
);

# --------------------------------------------------------------------------------------------------

#pod =option local_release
#pod
#pod If true, release will be a local one, i. e. no external operations will be done: C<UploadToCPAN>
#pod and C<hg push> will be skipped, <hg tag> will create a local tag.
#pod
#pod Option can be set trough F<dist.ini> file or with C<DZIL_LOCAL_RELEASE> environment variable.
#pod
#pod Optional, default value is 0.
#pod
#pod =cut

has local_release => (
    is          => 'ro',
    isa         => 'Bool',
    lazy        => 1,
    default     => sub {
        my ( $self ) = @_;
        return $self->payload->{ local_release } // $ENV{ DZIL_LOCAL_RELEASE };
    },
);

# --------------------------------------------------------------------------------------------------

#pod =option archive
#pod
#pod Directory to archive files to. If empty, release will not be archived. If such directory does not
#pod exist, it will be created before release.
#pod
#pod Optional, default value C<".releases">.
#pod
#pod =cut

has archive => (
    is          => 'ro',
    isa         => 'Str',
    lazy        => 1,
    default     => sub {
        my ( $self ) = @_;
        return $self->payload->{ archive } // ".releases";
    },
);

# --------------------------------------------------------------------------------------------------

#pod =option templates
#pod
#pod This option will be passed to C<Templates> plugin. If you no not want C<Templates> to process
#pod files, specify C<:NoFiles>. This is multi-value option (i. e. may be specified several times).
#pod
#pod Optional, default value C<[ ':InstallModules' ]>.
#pod
#pod =cut

has templates => (
    is          => 'ro',
    isa         => 'Maybe[ArrayRef[Str]]',
    lazy        => 1,
    default     => sub {
        my ( $self ) = @_;
        my $templates = $self->payload->{ templates } // [ ':InstallModules' ];
        $templates = [ grep( { $_ ne  '' } @$templates ) ];   # Ignore empty items.
        if ( not @$templates ) {
            $templates = undef;
        };
        return $templates;
    },
);

# --------------------------------------------------------------------------------------------------

#pod =option unwanted_module
#pod
#pod =option unwanted_modules
#pod
#pod TODO C<[ qw{ DPP Data::Printer } ]>.
#pod
#pod =cut

has unwanted_modules => (
    isa         => 'ArrayRef[Str]',
    is          => 'ro',
    lazy        => 1,
    default     => sub {
        my ( $self ) = @_;
        my $p  = $self->payload;
        my $u1 = $p->{ unwanted_module  };
        my $u2 = $p->{ unwanted_modules };
        return $u1 || $u2 ? [ $u1 ? @$u1 : (), $u2 ? @$u2 : () ] : [ qw{ DPP Data::Printer } ];
    },
);

# --------------------------------------------------------------------------------------------------

#pod =option spellchecker
#pod
#pod Command to run spellchecker. Spellchecker command is expected to read text from stdin, and print to
#pod stdout misspelled words. If empty, spellchecking will be skipped. This involve C<Test::PodSpelling>
#pod plugin and internally implemented checking the F<Changes> file.
#pod
#pod Optional, default value C<aspell list -l en -p ./xt/aspell.en.pws>.
#pod
#pod =cut

has spellchecker => (
    is          => 'ro',
    isa         => 'Str',
    lazy        => 1,
    default     => sub {
        my ( $self ) = @_;
        return $self->payload->{ spellchecker } // 'aspell list -l en -p ./xt/aspell.en.pws';
            #   Leading dot (in `./xt/aspell.en.pws`) is important! Whitout the dot `aspell`
            #   fails to find the dictionary.
    },
);

# --------------------------------------------------------------------------------------------------

#pod =method mvp_multivalue_args
#pod
#pod =cut

sub mvp_multivalue_args {
    return qw{ templates unwanted_module unwanted_modules readme };
};

# --------------------------------------------------------------------------------------------------

#pod =method _quote
#pod
#pod Convert an attribute to a form suitable for using in source. C<Str> attribute is converted into a
#pod string literal, C<ArrayRef> attribute is converted to a list of string literals.
#pod
#pod =cut

sub _quote {
    my ( $self, @args ) = @_;
    my @names;
    for my $arg ( @args ) {
        for ( ref( $arg ) eq 'ARRAY' ? @$arg : $arg ) {
            my $name = $_;
            $name =~ s{([\\'])}{\$1}gx;
            push( @names, "'$name'" );
        };
    };
    return join( ', ', @names );
};

# --------------------------------------------------------------------------------------------------

# Helper func: Iterate through distribution prerequisities.

sub MY::prereqs($$) {                   ## no critic ( ProhibitSubroutinePrototypes )
    my ( $plugin, $callback ) = @_;
    my $prereqs = $plugin->zilla->prereqs->cpan_meta_prereqs;
    for my $phase ( $prereqs->__legal_phases ) {
        for my $type  ( $prereqs->__legal_types  ) {
            my $reqs = $prereqs->requirements_for( $phase, $type ) or next;
            for my $module ( keys( %{ $reqs->{ requirements } } ) ) {
                $callback->( $plugin, $module, $phase, $type, $reqs );
            };
        };
    };
    return;
};

sub MY::file {
    my ( $plugin, $name ) = @_;
    our $Self;                          ## no critic ( ProhibitPackageVars )
    my $data = $Self->merged_section_data;
    my $root = path( $plugin->zilla->root );
    my $file;
    if ( $root->child( $name )->exists ) {
        $file = Dist::Zilla::File::OnDisk->new( {
            name    => $name,
        } );
    } elsif ( $data->{ $name } ) {
        $file = Dist::Zilla::File::InMemory->new( {
            name    => $name,
            content => ${ $data->{ $name } },
        } );
    } else {
        croak "$name: file not found";
    };
    return $file;
};

# --------------------------------------------------------------------------------------------------

sub configure {

    my ( $self ) = @_;
    our $Self = $self;                  ## no critic ( ProhibitPackageVars )

    $self->add_plugins(

        [ 'Hook' => 'prologue' => {
            # DOES NOT WORK because plugin name will be '@Author::VDB/prologue'.
            'hook' => [ q{
                use autodie ':all';
                use IPC::System::Simple qw{ capture };
                use Path::Tiny;
            } ],
        } ],

        [ 'Hook::VersionProvider' => {
            'hook' => [ q{
                use Path::Tiny;
                my $file = path( $zilla->root )->child( 'VERSION' );
                my $version = $file->slurp =~ s{\s+\z}{}r;
                if ( $version !~ m{\A@{ [ Perl::Version::REGEX ] }\z} ) {
                    $self->abort( [
                        'Invalid version string "%s" at %s line 1', $version, "$file"
                    ] );
                };
                $version;
            } ],
        } ],

        [ 'Hook::Init' => 'my vars' => {
            'hook' => [ q{
                pack} . q{age MY {          # Hide declaration from `new-version.t`.
                    our $name           = $dist->name;
                    our $package        = $name =~ s{-}{::}gr;
                    our $version        = $dist->version;
                    our $Abstract       = $dist->abstract;
                    our $abstract       = lcfirst( $Abstract );
                    our $author         = $dist->authors->[ -1 ];
                    our $metacpan       = "https://metacpan.org/release/$name";
                    our $cpan_rt_mailto = "mailto:bug-$name\@rt.cpan.org";
                    our $cpan_rt_browse = "https://rt.cpan.org/Public/Dist/Display.html?Name=$name";
                    our $cpan_rt_report = "https://rt.cpan.org/Public/Bug/Report.html?Queue=$name";
                    our $repo_type      = "hg";
                    our $repo_host      = "fedorapeople.org";
                    our $repo_url       = "https://vandebugger.$repo_host/hg/perl-$name";
                    our $repo_web       = undef;
                    our $repo_clone     = "$repo_type clone $repo_url" .
                        ( $repo_url =~ m{/\Qperl-$name\E\z} ? '' : " \\\n        perl-$name" );
                    our $bundle         = $Dist::Zilla::PluginBundle::Author::VDB::Self;
                };
                $ENV{ 'TEST_FIX' . 'ME_FORMAT' } = 'perl';      # Hide keyword from `fixme.t`.
            } ],
        } ],

        #
        #   Files to include
        #

        [ 'Manifest::Read' ],

        #
        #   Generated files
        #

        $self->copying ne '' ? (
            [ 'GenerateFile' => 'COPYING' => {
                'filename' => 'COPYING',
                'content'  => [
                    q[{] . q[{],
                    q[    include( MY::file( $plugin, ] . $self->_quote( $self->copying ) . q[ ) )],
                    q[        ->fill_in],
                    q[        ->pod2text( width => 80, indent => 0, loose => 1, quotes => 'none' );],
                    q[}] . q[}],
                ],
            } ],
        ) : (
        ),

        $self->readme ? (
            [ 'GenerateFile' => 'README' => {
                'filename' => 'README',
                'content'  => [
                    q[{] . q[{],
                    q[    for my $file ( ] . $self->_quote( $self->readme ) . q[ ) {],
                    q[        $OUT .= include( MY::file( $plugin, $file ) )],
                    q[            ->fill_in],
                    q[            ->pod2text( width => 80, indent => 0, loose => 1, quotes => 'none' );],
                    q[    };],
                    q[}] . q[}],
                ],
            } ],
        ) : (
        ),

        [ 'Manifest::Write' => {
            'source_providers' => [
                'Manifest::Read',
                'GenerateFile',
            ],
        } ],

        #
        #   File mungers
        #

        [ 'Templates' => { # REQUIRE: v0.5.0 for including `Dist::Zilla::File` objects.
            'templates' => [
                '@Author::VDB/src doc',
                @{ $self->templates // [] },
            ],
        } ],

        [ 'OurPkgVersion' ],

        [ 'SurgicalPodWeaver' => {
            'config_plugin' => '@Author::VDB',
            'replacer'      => 'replace_with_comment',
        } ],

        [ 'FileFinder::ByName' => 'src doc' => {
            # Plugin name will be `@Author::VDB/doc`.
            'file' => [
                $self->copying ne '' ? ( 'COPYING' ) : (),
                $self->readme        ? ( 'README'  ) : (),
            ],
        } ],

        #
        #   Update sources
        #

        #   Copy built doc files back to source directory.
        [ 'Hook::AfterBuild' => 'update src doc' => {
            'hook' => [ q{
                use Path::Tiny;
                my $files = $zilla->plugin_named( '@Author::VDB/src doc' )->find_files();
                my $build = path( $arg->{ build_root } );
                my $root  = path( $dist->root );
                for my $file ( @$files ) {
                    my $new_file = $build->child( $file->name );
                    my $old_file = $root->child( $file->name );
                    my $new_bulk = $new_file->slurp;
                    my $old_bulk = $old_file->exists ? $old_file->slurp : undef;
                    if ( not defined( $old_bulk ) or $new_bulk ne $old_bulk ) {
                        $self->log( [ 'updating %s', $file->name ] );
                        $old_file->spew( $new_bulk );
                    };
                };
            } ],
        } ],

        #
        #   Tests
        #

        [ 'Test::DiagINC' ],

        # Files

        [ 'Test::EOL' => {
            'finder' => '@Author::VDB/Manifest::Read',
        } ],

        [ 'Test::NoTabs' => {
            'finder' => '@Author::VDB/Manifest::Read',
        } ],

        [ 'MojibakeTests' ],

        # Code

        [ 'Test::Compile' => {
            'fake_home' => 1,
        } ],

        [ 'Test::Version' => {          # All modules have version.
            #~ 'is_strict' => 1,        # Does not work for trial releases.
        } ],

        [ 'Test::NewVersion' ],         # This is not a version already uploaded to CPAN.

        [ 'Test::MinimumVersion' => {
            'max_target_perl' => $self->minimum_perl,
        } ],

        [ 'Test::Fixme' ],

        [ 'Test::Perl::Critic' => {
            'critic_config' => 'xt/perlcritic.ini',
                #   The test does not check tests. TODO: How to fix?
        } ],

        # POD

        [ 'PodSyntaxTests'   ],     # `Dist-Zilla`-bundled test, uses `Test::pod`.

        [ 'PodCoverageTests' ],     # `Dist-Zilla`-bundled test, uses `Pod::Coverage::TrustPod`.

        $self->spellchecker ? (
            [ 'Test::PodSpelling' => {
                'spell_cmd' => $self->spellchecker,
            } ],
        ) : (
        ),

        [ 'Test::Pod::No404s' ],    # No dead URLs.

        # Metadata

        [ 'MetaTests' ],    # `Dist-Zilla`-bundled test, uses `Test::CPAN::Meta`, checks `META.yml`.

        [ 'Test::CPAN::Meta::JSON' ],   # Uses `Test::CPAN::Meta::JSON`.

        [ 'Test::CPAN::Changes' ],
            #   Does not check that `Changes` has a record for current version, see
            #   <https://github.com/doherty/Dist-Zilla-Plugin-Test-CPAN-Changes/issues/6>.

        [ 'Test::DistManifest' ],

        # Overall

        [ 'Test::Kwalitee' ],

        #
        #   Metainfo
        #

        [ 'MinimumPerl' ],

        [ 'AutoPrereqs' ],

        #   `Prereqs::AuthorDeps` has a problem:
        #       <https://github.com/dagolden/Dist-Zilla-Plugin-Prereqs-AuthorDeps/issues/1>
        #   It adds local plugins (e. g. tools::GenerateHooks) to the dependencies,
        #   which obviously are not indexed on CPAN.
        [ 'Prereqs::AuthorDeps' => {
            #~ 'exclude' => [
                #~ #   Exclude option requires a list of specific files, while I want to ignore all
                #~ #   files in specific directory.
            #~ ],
        } ],

        #   TODO: Remove when possible.
        #   This is a dirty hack. Remove it when `Prereqs::AuthorDeps` allows me to ignore all the
        #   modules from `tools/` directory. Meanwhile, find and remove all the dependencies on
        #   modules with `tools::` prefix.
        [ 'Hook::PrereqSource' => 'tools' => {
            'hook' => [ q{
                MY::prereqs( $self, sub {
                    my ( $self, $module, $phase, $type, $reqs ) = @_;
                    if ( $module =~ m{^tools::} ) {
                        $self->log_debug( [
                            'found dependency on module %s (phase %s, type %s), deleting it',
                            $module, $phase, $type
                        ] );
                        delete( $reqs->{ requirements }->{ $module } );
                    };

                } );
            } ],
        } ],

        #   `use autodie ':all';` implicitly requires `IPC::System::Simple` module, but this
        #   dependency is not detected by `AutoPrereqs`. If there is dependency on `autodie`, let
        #   us add dependency on `IPC::System::Simple`.
        [ 'Hook::PrereqSource' => 'autodie' => {
            'hook' => [ q{
                MY::prereqs( $self, sub {
                    my ( $self, $module, $phase, $type ) = @_;
                    if ( $module eq 'autodie' ) {
                        $self->log_debug( [
                            'found dependency on module %s (phase %s, type %s), ' .
                                'adding dependency on IPC::System::Simple',
                            $module, $phase, $type
                        ] );
                        $dist->register_prereqs(
                            { phase => $phase, type => $type },
                            'IPC::System::Simple' => 0,
                        );
                    };
                } );
            } ],
        } ],

        [ 'CheckPrereqsIndexed'   ],    # Make sure all prereqs are published in CPAN.

        [ 'MetaProvides::Package' ],

        [ 'MetaResources::Template' => {
            'delimiters'          => '{ }',
            'homepage'            => '{$MY::metacpan}',
            'license'             => '{$dist->license->url}',
            'repository.type'     => '{$MY::repo_type}',
            'repository.url'      => '{$MY::repo_url}',
            'repository.web'      => '{$MY::repo_web}',
            'bugtracker.mailto'   => '{$MY::cpan_rt_mailto}',
            'bugtracker.web'      => '{$MY::cpan_rt_browse}',
        } ],

        [ 'MetaYAML' ],                 # Generate `META.yml`.

        [ 'MetaJSON' ],                 # Generate `META.json`.

        #
        #   Installer
        #

        [ 'ModuleBuildTiny' ],

        #
        #   Release
        #

        $self->archive ? (
            #   Make sure archive directory exists. Do it in the very beginnig of release because
            #   it is simple and fast operation. It will be annoying to pass all the tests and
            #   stop release  because `dzil` fails to create archive directory.
            [ 'Hook::BeforeRelease' => 'archive directory' => {
                'hook' => [ q{
                    my $root = path( $self->zilla->root . '' );
                    my $dir = path( "} . $self->archive . q{" );
                    if ( $dir->is_absolute ) {
                        $self->log_error( [ 'bad archive directory: %s', "$dir" ] );
                        $self->log_error( [ 'absolute path not allowed' ] );
                        $self->abort();
                    };
                    if ( not $dir->is_dir ) {
                        $self->log( [ 'creating archive directory %s', "$dir" ] );
                        $dir->mkpath();
                    };
                } ],
            } ]
        ) : (
        ),

        #~ [ 'RunExtraTests' ],         # Does not work for me, see <https://github.com/dagolden/Dist-Zilla-Plugin-CheckExtraTests/issues/26>.
        [ 'CheckExtraTests' ],          # So use CheckExtraTest meanwhile.

        [ 'TestRelease'     ],          # Unpack tarball and run tests.

        #   Make sure the distro does not depend on unwanted modules. Unwanted module, for example,
        #   is `Data::Printer`. I use it often for debugging purposes and forget to remove
        #   debugging code.
        [ 'Hook::BeforeRelease' => 'unwanted deps' => { # REQUIRTE: HHH
            'hook' => [ q{
                my @unwanted = (} . $self->_quote( $self->unwanted_modules ) . q{);
                my %unwanted = map( { $_ => 1 } @unwanted );
                my $heading = 'unwanted modules found:';
                MY::prereqs( $self, sub {
                    my ( $self, $module, $phase, $type ) = @_;
                    if ( $unwanted{ $module  } ) {
                        if ( $heading ) {
                            $self->log_error( $heading );
                            $heading = undef;
                        };
                        $self->log_error( [
                            '    %s (phase %s, type %s)', $module, $phase, $type
                        ] );
                    };
                } );
                $self->abort_if_error();
            } ],
        } ],

        [ 'CheckChangesHasContent' ],

        $self->spellchecker ? (
            [ 'Hook::BeforeRelease' => 'spellcheck changes' => {
                'hook' => [ q{
                    $self->log( 'spellchecking Changes' );
                    use File::chdir;
                    local $CWD = $zilla->built_in . '';
                    #
                    #   Run spellchecker, collect list of unknown words.
                    #
                    use IPC::Run3;
                    my @list;
                    run3( '} . $self->spellchecker . q{', 'Changes', \@list );
                    if ( $? > 0 ) {
                        $self->abort();
                    };
                    chomp( @list );
                    #
                    #   Steal list of words to ignore from `PodSpelling` plugin.
                    #
                    my $podspelling = $zilla->plugin_named( '@Author::VDB/Test::PodSpelling' );
                    my %ignore = map( { $_ => 1 } @{ $podspelling->stopwords } );
                    #
                    #   Add all module names.
                    #
                    my $prereqs = $dist->prereqs->cpan_meta_prereqs;
                    for my $phase ( $prereqs->__legal_phases ) {
                    for my $type  ( $prereqs->__legal_types  ) {
                        my $reqs = $prereqs->requirements_for( $phase, $type ) or next;
                        for my $module ( keys( %{ $reqs->{ requirements } } ) ) {
                            $ignore{ $_ } = 1 for split( '::', $module );
                        };
                    };
                    };
                    #
                    #   Build maps: word => count and count => word.
                    #
                    my %w2c;  # word => count
                    $w2c{ $_ } += 1 for grep( { not $ignore{ $_ } and not $ignore{ lc( $_ ) } } @list );
                    my %c2w;   # count => word
                    push( @{ $c2w{ $w2c{ $_ } } }, $_ ) for keys( %w2c );
                    #
                    #   Now print the list of spelling errors.
                    #
                    for my $count ( sort( { $b <=> $a } keys( %c2w ) ) ) {
                        printf( "%2d: %s\n", $count, join( ', ', sort( @{ $c2w{ $count } } ) ) );
                    };
                    if ( %w2c ) {
                        $self->abort( 'spelling errors found in Changes' );
                    };
                } ],
            } ],
        ) : (
        ),

        #   Make sure there is no such tag yet, neither global nor local.
        [ 'Hook::BeforeRelease' => 'hg check tag' => {
            'hook' => [ q{
                use Path::Tiny;
                my $version = $dist->version;
                my $tags = path( '.hgtags' );
                if ( $tags->slurp_utf8 =~ m{ \Q$version\E$}m ) {
                    $self->abort( [ 'tag %s already exists', $version ] );
                };
                my $ltags = path( '.hg/localtags' );
                if ( $ltags->exists and $ltags->slurp_utf8 =~ m{ \Q$version\E$}m ) {
                    $self->abort( [ 'local tag %s already exists', $version ] );
                };
            } ],
        } ],

        [ 'Hook::BeforeRelease' => 'hg stat' => {
            'hook' => [ q{
                #   Make sure all the files committed.
                use IPC::System::Simple 'capture';
                my @stdout = capture( 'hg', 'status' );
                if ( @stdout ) {
                    chomp( @stdout );
                    $self->log_error( 'hg status:' );
                    $self->log_error( "    $_" ) for @stdout;
                    $self->abort( 'hg status is not clean' );
                };
            } ],
        } ],

        $self->local_release ? (
            [ 'Hook::BeforeRelease' => 'release note' => {
                'hook' => [ q{
                    $self->log( '*** Preparing to *local* release ***' );
                } ],
            } ],
        ) : (
        ),
        [ 'ConfirmRelease' ],       # Ask confirmation before uploading the release.

        [ 'Hook::Releaser' => 'tgz' => {
            'hook' => [ q{
                $MY::tgz = $arg;
            } ],
        } ],

        #   `Archive` is a good plugin, but I need to copy, not move tarball, because I want to
        #   archive the tarball first, and then upload it to CPAN.
        $self->archive ? (
            [ 'Hook::Releaser' => 'archive release' => {
                'hook' => [ q{
                    use Path::Tiny;
                    my $tgz = path( $arg );
                    my $dir = path( "} . $self->archive . q{" );
                    $self->log( [ 'copying %s to %s', "$tgz", "$dir" ] );
                    $tgz->copy( $dir->child( $tgz->basename ) );
                } ],
            } ],
        ) : (
        ),

        $self->local_release ? (
            # No need in `FakeRelease`: we have at least one releaser, it is enough.
        ) : (
            [ 'UploadToCPAN' ],
        ),

        [ 'Hook::AfterRelease' => 'hg add tag' => {
            'hook' => [ q{
                use IPC::System::Simple 'capture';
                my @stdout = capture( 'hg', 'id', '--debug' );
                chomp( @stdout );
                @stdout == 1 and $stdout[ 0 ] =~ m{^([0-9a-f]{40})(\+?) tip$} or do {
                    $self->log_error( 'fail to parse "hg id" output:' );
                    $self->log_error( "    $_" ) for @stdout;
                    $self->abort();
                };
                my ( $id, $dirty ) = ( $1, $2 );
                if ( $dirty ) {
                    $self->abort( 'hg status is not clean' );
                };
                path( '} . ( $self->local_release ? '.hg/localtags' : '.hgtags' ) . q{' )->append( sprintf( "%s %s\n", $id, $dist->version ) );
            } ],
        } ],

        [ 'NextRelease' => {
            'format'      => '%V @ %{yyyy-MM-dd HH:mm zzz}d',
            'time_zone'   => 'UTC',
        } ],

        [ 'Hook::AfterRelease' => 'bump version' => {
            'hook' => [ q{
                use Path::Tiny;
                use Perl::Version;
                my $version = Perl::Version->new( $dist->version );
                $version->inc_alpha();
                path( $zilla->root )->child( 'VERSION' )->spew( $version );
                $self->log( [ 'next release will be %s', "$version" ] );
            } ],
        } ],

        [ 'Hook::AfterRelease' => 'hg post-commit' => {
            'hook' => [ q{
                use autodie ':all';
                system(
                    'hg', 'commit', '-m', 'post-release',
                        qw{ .hgtags Changes VERSION }
                );
            } ],
        } ],

        $self->local_release ? (
        ) : (
            [ 'Hook::AfterRelease' => 'hg push' => {
                'hook' => [ q{
                    use IPC::System::Simple 'capture';
                    my @stdout = capture( 'hg', 'push' );
                    chomp( @stdout );
                    $self->log( "    $_" ) for @stdout;
                } ],
            } ],
        ),

        [ 'Hook::AfterRelease' => 'install' => {
            'hook' => [ q{
                use autodie ':all';
                use File::chdir;
                $self->log( [ 'installing %s-%s', $dist->name, $dist->version ] );
                local $CWD = $zilla->built_in;
                system( 'cpanm', '--notest', '.' );
                    # ^ We run the tests on unpacked tarball before release, no need in running
                    #   tests one more time.
            } ],
        } ],

        [ 'Hook::AfterRelease' => 'clean' => {
            'hook' => [ q{
                $zilla->clean();
            } ],
        } ],

    );

    return;

};

__PACKAGE__->meta->make_immutable();

1;

# doc/what.pod #

#pod =encoding UTF-8
#pod
#pod =head1 WHAT?
#pod
#pod C<Dist-Zilla-PluginBundle-Author-VDB> (or just C<@Author::VDB>) is a C<Dist-Zilla> plugin bundle used by VDB.
#pod
#pod =cut

# end of file #
# doc/why.pod #

#pod =encoding UTF-8
#pod
#pod =head1 WHY?
#pod
#pod I have published few distributions on CPAN. Every distribution have F<dist.ini> file. All the
#pod F<dist.ini> files are very similar. Maintaining multiple very similar F<dist.ini> files is boring.
#pod Plugin bundle solves the problem.
#pod
#pod =cut

# end of file #

=pod

=encoding UTF-8

=head1 NAME

Dist::Zilla::PluginBundle::Author::VDB - VDB's plugin bundle

=head1 VERSION

Version v0.8.2, released on 2015-09-26 11:17 UTC.

=head1 WHAT?

C<Dist-Zilla-PluginBundle-Author-VDB> (or just C<@Author::VDB>) is a C<Dist-Zilla> plugin bundle used by VDB.

=head1 DESCRIPTION

It is unlikely that someone else will want to use it, so I will not bother with documenting it, at
least for now.

=head1 OBJECT METHODS

=head2 mvp_multivalue_args

=head2 _quote

Convert an attribute to a form suitable for using in source. C<Str> attribute is converted into a
string literal, C<ArrayRef> attribute is converted to a list of string literals.

=head1 OPTIONS

=head2 minimum_perl

Optional, default value C<5.006>.

=head2 copying

Name of POD file to generate distribution F<COPYING> text file. Empty value disables generation
F<COPYING> file.

C<Str>, optional, default value C<doc/copying.pod>.

=head2 readme

Names of POD files to generate distribution F<README> text file. This is a multi-value option.
Empty values are ignored. Empty list disables generating F<README>file.

C<ArrayRef[Str]>, optional, default value C<[ 'doc/what.pod', 'doc/why.pod', 'doc/naming.pod', 'doc/forms.pod', 'doc/source.pod', 'doc/distribution.pod', 'doc/installing.pod', 'doc/hacking.pod', 'doc/documentation.pod', 'doc/feedback.pod', 'doc/glossary.pod' ]>.

=head2 local_release

If true, release will be a local one, i. e. no external operations will be done: C<UploadToCPAN>
and C<hg push> will be skipped, <hg tag> will create a local tag.

Option can be set trough F<dist.ini> file or with C<DZIL_LOCAL_RELEASE> environment variable.

Optional, default value is 0.

=head2 archive

Directory to archive files to. If empty, release will not be archived. If such directory does not
exist, it will be created before release.

Optional, default value C<".releases">.

=head2 templates

This option will be passed to C<Templates> plugin. If you no not want C<Templates> to process
files, specify C<:NoFiles>. This is multi-value option (i. e. may be specified several times).

Optional, default value C<[ ':InstallModules' ]>.

=head2 unwanted_module

=head2 unwanted_modules

TODO C<[ qw{ DPP Data::Printer } ]>.

=head2 spellchecker

Command to run spellchecker. Spellchecker command is expected to read text from stdin, and print to
stdout misspelled words. If empty, spellchecking will be skipped. This involve C<Test::PodSpelling>
plugin and internally implemented checking the F<Changes> file.

Optional, default value C<aspell list -l en -p ./xt/aspell.en.pws>.

=head1 WHY?

I have published few distributions on CPAN. Every distribution have F<dist.ini> file. All the
F<dist.ini> files are very similar. Maintaining multiple very similar F<dist.ini> files is boring.
Plugin bundle solves the problem.

=for Pod::Coverage configure

=head1 AUTHOR

Van de Bugger <van.de.bugger@gmail.com>

=head1 COPYRIGHT AND LICENSE

Copyright © 2015 Van de Bugger

This file is part of perl-Dist-Zilla-PluginBundle-Author-VDB.

perl-Dist-Zilla-PluginBundle-Author-VDB is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later version.

perl-Dist-Zilla-PluginBundle-Author-VDB is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
perl-Dist-Zilla-PluginBundle-Author-VDB. If not, see <http://www.gnu.org/licenses/>.

=cut

__DATA__

__[ doc/copying.pod ]__

=encoding UTF-8

=head1 COPYRIGHT AND LICENSE

Copyright Â© 2015 Van de Bugger

C<perl-{{$MY::name}}> is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software Foundation, either
version 3 of the License, or (at your option) any later version.

C<perl-{{$MY::name}}> is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
C<perl-{{$MY::name}}>. If not, see L<http://www.gnu.org/licenses/>.

C<perl-{{$MY::name}}> I<distribution> may contain files generated by C<Dist-Zilla> and/or its
plugins from third-party templates; copyright and license specified above are I<not> applicable to
that files.

=cut

__[ doc/distribution.pod ]__

=encoding UTF-8

=head1 DISTRIBUTION

C<{{$MY::name}}> distributions are published on L<CPAN|{{$MY::metacpan}}>.

=head2 Generated Files

Distribution may contain files preprocessed or generated by C<Dist-Zilla> and its plugins. Some
generated files are made from C<{{$MY::name}}> source, but some are generated from
third-party templates. Files generated from third-party templates usually include a comment near
the top of the file:

    This file was generated with NAME

(where I<NAME> is a name of the plugin generated the file). Such files are I<not> part of
C<{{$MY::name}}> source, and C<{{$MY::name}}> copyright and license are not applicable
to such files.

=cut

__[ doc/documentation.pod ]__

=encoding UTF-8

=head1 DOCUMENTATION

=head2 Online

The easiest way is browsing the documentation L<online at meta::cpan|{{$MY::metacpan}}>.

=head2 Locally Installed

If you have the distribution installed, use C<perldoc> tool to browse locally
installed documentation:

    $ perldoc {{$MY::package}}::Manual
    $ perldoc {{$MY::package}}

=head2 Built from Source

Build C<{{$MY::name}}> first (see L</"HACKING">), then:

    $ cd {{$MY::name}}-VERSION
    $ perldoc {{$MY::package}}::Manual
    $ perldoc {{$MY::package}}

where I<VERSION> is a version of built distribution.

=cut

__[ doc/feedback.pod ]__

=encoding UTF-8

=head1 FEEDBACK

=head2 CPAN Request Tracker

The quickest way to report a bug in C<{{$MY::name}}> is by sending email to
bug-{{$MY::name}} [at] rt.cpan.org.

CPAN request tracker can be used via web interface also:

=over

=item L<Browse bugs|{{$MY::cpan_rt_browse}}>

Browsing bugs does not require authentication.

=item L<Report bugs|{{$MY::cpan_rt_report}}>

You need to be a CPAN author, have a L<BitCard|https://www.bitcard.org/> account, or OpenID in
order to report bugs via the web interface.

(On 2015-04-27 I have logged in successfully with my LiveJournal OpenID, but my Google OpenID did
not work for CPAN. I did not check other OpenID providers.)

=back

=head2 Send Email to Author

As a last resort, send email to author: {{$MY::author}}. Please start message subject with
"perl-{{$MY::name}}:".

=cut

__[ doc/forms.pod ]__

=encoding UTF-8

=head1 FORMS

You may face C<{{$MY::name}}> in I<source> or I<distribution> forms.

If you are going to {{$MY::abstract}}, you will likely be interested in I<using>
C<{{$MY::name}}> I<distribution>. If you are going to I<develop> (or I<hack>) the
C<{{$MY::name}}> itself, you will likely need the I<source>, not distribution.

Since Perl is an interpreting language, modules in the distribution I<look> like sources. Actually,
they are Perl source files. But they are not I<actual> sources, because they are I<built>
(preprocessed or generated) by L<Dist-Zilla>.

How to distinguish source and distribution:

=over

=item *

Source may contain Mercurial files and directories F<.hgignore>, F<.hgtags>, F<.hg/>, while
distribution should not.

=item *

Source should contain files F<dist.ini>, F<weaver.ini>, while distribution may not.

=item *

Source should I<not> contain F<xt/> directory, while distribution should.

=item *

Name of source directory does I<not> include version (e. g. C<{{$MY::name}}>), while name of
distribution does (e. g. C<{{$MY::name}}-0.007>).

=back

=cut

__[ doc/glossary.pod ]__

=encoding UTF-8

=head1 GLOSSARY

=over

=item CPAN

Comprehensive Perl Archive Network, a B<large> collection of Perl software and documentation. See
L<cpan.org|http://www.cpan.org>, L<What is
CPAN?|http://www.cpan.org/misc/cpan-faq.html#What_is_CPAN>.

=item Distribution

Tarball, containing Perl modules and accompanying files (documentation, metainfo, tests). Usually
distributions are uploaded to CPAN, and can be installed with dedicated tools (C<cpan>, C<cpanm>,
and others).

=item Module

Perl library file, usually with C<.pm> suffix. Usually contains one package. See
L<perlmod|http://perldoc.perl.org/perlmod.html#Perl-Modules>.

=item Package

Perl language construct. See L<package|http://perldoc.perl.org/functions/package.html> and
L<perlmod|http://perldoc.perl.org/perlmod.html#Packages>.

=back

=cut

__[ doc/hacking.pod ]__

=encoding UTF-8

=head1 HACKING

For hacking, you will need Mercurial, Perl interpreter and C<Dist-Zilla> (with some plugins), and
likely C<cpanm> to install missed parts.

Clone the repository first:

    $ {{$MY::repo_clone}}
    $ cd perl-{{$MY::name}}

To build a distribution from the source, run:

    $ dzil build

If required C<Dist-Zilla> plugins are missed, C<dzil> tool will warn you and show the command to
install all the required plugins, e. g.:

    Required plugin Dist::Zilla::Plugin::Test::EOL isn't installed.

    Run 'dzil authordeps' to see a list of all required plugins.
    You can pipe the list to your CPAN client to install or update them:

        dzil authordeps --missing | cpanm

To run the tests:

    $ dzil test

To run all the tests, including release tests:

    $ dzil test --release

To install the distribution:

    $ dzil install

or

    $ cpanm ./{{$MY::name}}-VERSION.tar.gz

where I<VERSION> is a version of built distribution.

To clean the directory:

    $ dzil clean

=cut

__[ doc/installing.pod ]__

=encoding UTF-8

=head1 INSTALLING

=head2 With C<cpanm>

C<cpanm> tool is (probably) the easiest way to install distribution. It automates downloading,
building, testing, installing, and uninstalling.

To install the latest version from CPAN:

    $ cpanm {{$MY::package}}

To install a specific version (e. g. I<0.007>) from CPAN:

    $ cpanm {{$MY::package}}@0.007

To install locally available distribution (e. g. previously downloaded from CPAN or built from
sources):

    $ cpanm ./{{$MY::name}}-0.007.tar.gz

To uninstall the distribution:

    $ cpanm -U {{$MY::package}}

=head2 Manually

To install distribution tarball manually (let us assume you have version I<0.007> of the
distribution):

    $ tar xaf {{$MY::name}}-0.007.tar.gz
    $ cd {{$MY::name}}-0.007
    $ perl Build.PL
    $ ./Build build
    $ ./Build test
    $ ./Build install

=head2 See Also

L<How to install CPAN modules|http://www.cpan.org/modules/INSTALL.html>

=cut

__[ doc/naming.pod ]__

=encoding UTF-8

=head1 NAMING

C<perl-{{$MY::name}}> is official software name.

However, in Perl world prefix "perl-" is redundant and not used. For example, on
L<meta::cpan|https://metacpan.org/> this software is named as C<{{$MY::name}}>. In the rest
of the documentation shortened name C<{{$MY::name}}> is used as synonym for full name
C<perl-{{$MY::name}}>. We are in the Perl world, aren't we?

You may notice that name may be spelled with dashes (C<{{$MY::name}}>) or with double colons
(C<{{$MY::package}}>). Strictly speaking, there is difference: the first one is software
name, while the second is name of Perl package, but often these names are interchangeable
especially if software consists of single package.

=cut

__[ doc/source.pod ]__

=encoding UTF-8

=head1 SOURCE

C<{{$MY::name}}> source is in Mercurial repository hosted on {{$MY::repo_host}}.
{{$MY::repo_web ? "You can either L<browse the source online|{{$MY::repo_web}}> or " : "To
"}} clone the entire repository:

    $ {{$MY::repo_clone}}

=head2 Source Files

C<{{$MY::name}}> source files usually include a comment near the top of the file:

    This file is part of perl-{{$MY::name}}.

Not all source files are included into distribution. Some source files are used at distribution
build time only, and not required for installation.

=cut

__END__

# end of file #
