#   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
#   file: lib/Dist/Zilla/Plugin/Manifes/Write.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-Plugin-Manifest-Write.
#pod
#pod perl-Dist-Zilla-Plugin-Manifest-Write 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-Plugin-Manifest-Write 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-Plugin-Manifest-Write. If not, see <http://www.gnu.org/licenses/>.
#pod
#pod =cut

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

#pod =for :this This is C<Dist::Zilla::Plugin::Manifest::Write> module documentation. Read this if you are going to hack or
#pod extend C<Manifest::Write>.
#pod
#pod =for :those If you want to have annotated MANIFEST in your distribution, read the L<manual|Dist::Zilla::Plugin::Manifest::Write::Manual>. General
#pod topics like getting source, building, installing, bug reporting and some others are covered in the
#pod F<README>.
#pod
#pod =head1 DESCRIPTION
#pod
#pod C<Dist::Zilla::Plugin::Manifest::Write> class plays C<Dist::Zilla::Role::FileGatherer> role. To perform the role, the
#pod class implements C<gather_files> method. Other methods are supporting helpers for this one.
#pod
#pod Most of attributes are initialized by builder for easier customization by subclassing. Code is
#pod also divided into small methods for the same purpose.
#pod
#pod =head1 SEE ALSO
#pod
#pod =for :list
#pod = L<Dist::Zilla>
#pod = L<Dist::Zilla::Role>
#pod = L<Dist::Zilla::Role::Plugin>
#pod = L<Dist::Zilla::Role::FileInjector>
#pod = L<Dist::Zilla::Role::FileGatherer>
#pod = L<Dist::Zilla::Plugin::Manifest>
#pod = L<Dist::Zilla::Plugin::Manifest::Write::Manual>
#pod
#pod =cut

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

package Dist::Zilla::Plugin::Manifest::Write;

use Moose;          # Let `perlcritic` shut up: it complains on code (`$VERSION`) before `strict`.
use namespace::autoclean;

# PODNAME: Dist::Zilla::Plugin::Manifest::Write
# ABSTRACT: Have annotated MANIFEST in your distribution
our $VERSION = 'v0.8.1'; # VERSION

with 'Dist::Zilla::Role::FileGatherer';
with 'Dist::Zilla::Role::BeforeBuild';
with 'Dist::Zilla::Role::ErrorLogger';

use Dist::Zilla::File::FromCode;
use List::Util;
use Try::Tiny;

use Dist::Zilla::Role::File 5.023 ();       # Hint for `AutoPrereqs`.
    #   We do not consume the role but just require the specified version. Before this version
    #   `_added_by` was a `Str`, not `ArrayRef`.

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

#pod =head1 FUNCTIONS
#pod
#pod I would expect to find these functions in C<Dist::Zilla>. Actually, C<Dist::Zilla::Util> defines
#pod the function C<expand_config_package_name>, but that function "is likely to change or go away" and
#pod there is no reverse transformation.
#pod
#pod =cut

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

#pod =func __plugin_name
#pod
#pod     $str = __plugin_name( 'Dist::Zilla::Plugin::Name' );    # 'Name'
#pod     $str = __plugin_name( $plugin );
#pod
#pod The function takes either reference to a plugin object or a string, package name, and returns
#pod C<Dist::Zilla> plugin name: If its package name begins with C<Dist::Zilla::Plugin::>, this common
#pod prefix is dropped, otherwise the package name prepended with C<=>.
#pod
#pod =cut

sub __plugin_name($) {      ## no critic ( ProhibitSubroutinePrototypes )
    my ( $arg ) = @_;
    my $blessed = blessed( $arg );
    if ( $blessed ) {
        $arg = $blessed;
    };
    $arg = ( $arg =~ s{^Dist::Zilla::Plugin::}{}x ? '' : '=' ) . $arg;
    return $arg;
};

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

#pod =func __package_name
#pod
#pod     $str = __package_name( 'Name' );  # 'Dist::Zilla::Plugin::Name'
#pod     $str = __package_name( $plugin );
#pod
#pod This is operation opposite to C<__plugin_name>. It takes either reference to plugin object, or
#pod string, plugin name, and returns package name.
#pod
#pod This function is similar to C<expand_config_package_name> from C<Dist::Zilla::Util>, with minor
#pod difference: this function works with plugins only (not with plugin bundles and stashes), and
#pod accepts also reference to plugin objects.
#pod
#pod =cut

sub __package_name($) {     ## no critic ( ProhibitSubroutinePrototypes )
    my ( $arg ) = @_;
    my $blessed = blessed( $arg );
    if ( $blessed ) {
        return $blessed;
    };
    return ( $arg =~ s{^=}{}x ? '' : 'Dist::Zilla::Plugin::' ) . $arg;
};

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

#pod =Method BUILDARGS
#pod
#pod The method splits C<source_providers> and C<metainfo_providers> options. It allows user to specify
#pod multiple plugin names in one line in F<dist.ini> file, e. g.:
#pod
#pod     [Manifest::Write]
#pod         source_providers = GatherDir GatherDir::Template
#pod
#pod =cut

around BUILDARGS => sub {
    my ( $orig, $class, $args ) = @_;
    for my $providers ( qw{ source_providers metainfo_providers } ) {
        if ( exists( $args->{ $providers } ) ) {
            $args->{ $providers } = [
                map( { split( qr{\s+}x, $_ ) } @{ $args->{ $providers } } )
            ];
        };
    };
    return $class->$orig( $args );
};

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

#pod =attr strict
#pod
#pod Strictness of checking source and metainfo provider names: -1 (no checks), 0 (some mistakes are
#pod fatal, some are not), or 1 (all mistakes are fatal).
#pod
#pod C<Int>, read-only. Default is 1.
#pod
#pod See L<Dist::Zilla::Plugin::Manifets::Write::Manual/"strict">.
#pod
#pod =cut

has strict => (
    is      => 'ro',
    isa     => 'Int',
    default => 2,
);

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

#pod =attr manifest
#pod
#pod Name of manifest file to write.
#pod
#pod C<Str>, read-only. Default value is C<'MANIFEST'>.
#pod
#pod =cut

has manifest => (
    is      => 'ro',
    isa     => 'Str',
    default => 'MANIFEST',
);

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

#pod =attr source_list
#pod
#pod Array of source providers.
#pod
#pod C<ArrayRef[Str]>, read-only, default value is empty array. Init argument (and configure file
#pod option) name is C<source_providers>.
#pod
#pod =cut

has source_list => (
    is          => 'ro',
    isa         => 'ArrayRef[Str]',
    required    => 1,
    lazy        => 1,
    builder     => '_build_source_list',
    init_arg    => 'source_providers',
);

sub _build_source_list {
    return [];
};

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

#pod =attr metainfo_list
#pod
#pod Array of metainfo providers.
#pod
#pod C<ArrayRef[Str]>, read-only, default value is empty array. Init argument (and configure file
#pod option) name is C<metainfo_providers>.
#pod
#pod =cut

has metainfo_list => (
    is          => 'ro',
    isa         => 'ArrayRef[Str]',
    required    => 1,
    lazy        => 1,
    builder     => '_build_metainfo_list',
    init_arg    => 'metainfo_providers',
);

sub _build_metainfo_list {
    my ( $self ) = @_;
    my @list = ( qw{ MetaYAML MetaJSON Manifest Manifest::Write }, __plugin_name( $self ) );
    if ( $self->strict >= 0 ) {
        @list = grep( { $self->_does_role( $_, 'FileInjector' ) } @list );
    };
    return \@list;
};

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

#pod =attr source_hash
#pod
#pod Hash of source providers (plugin name => boolean), lazily initialized from C<source_list>.
#pod Introduced to make check "is plugin a source provider?" easier.
#pod
#pod C<HashRef[Str]>, read-only, not an init arg.
#pod
#pod =cut

has source_hash => (
    is       => 'ro',
    isa      => 'HashRef',
    required => 1,
    lazy     => 1,
    builder  => '_build_source_hash',
    init_arg => undef,
);

sub _build_source_hash {
    my ( $self ) = @_;
    return { map( { $_ => 1 } @{ $self->source_list } ) };
};

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

#pod =attr metainfo_hash
#pod
#pod Like C<source_hash>, but for metainfo providers.
#pod
#pod =cut

has metainfo_hash => (
    is       => 'ro',
    isa      => 'HashRef',
    required => 1,
    lazy     => 1,
    builder  => '_build_metainfo_hash',
    init_arg => undef,
);

sub _build_metainfo_hash {
    my ( $self ) = @_;
    my %hash = map( { $_ => 1 } @{ $self->metainfo_list } );
    return \%hash;
};

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

#pod =attr file_deeds
#pod
#pod "File deed" is a file ownership: if file belongs to a project, is a metainfo, or third-party file.
#pod
#pod This attribute maps internal file deed identifiers (C<source>, C<metainfo>, C<other>) to
#pod user-visible names used in manifest (project name, C<metainfo>, and C<3rd party> respectively).
#pod
#pod C<HashRef>, read-only.
#pod
#pod =cut

has file_deeds => (
    is       => 'ro',
    isa      => 'HashRef',
    lazy     => 1,
    builder  => '_build_file_deeds',
    init_arg => undef,
);

sub _build_file_deeds {
    my ( $self ) = @_;
    return {
        source   => $self->zilla->name,
        metainfo => 'metainfo',
        other    => '3rd party',
    };
};

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

#pod =attr file_deed_width
#pod
#pod Width of file deed column. Computed from file deed visible names.
#pod
#pod C<Int>, read-only.
#pod
#pod =cut

has file_deed_width => (
    is       => 'ro',
    isa      => 'Int',
    lazy     => 1,
    builder  => '_build_file_deed_width',
    init_arg => undef,
);

sub _build_file_deed_width {
    my ( $self ) = @_;
    return List::Util::max( map( { length( $_ ) } values( %{ $self->file_deeds } ) ) );
};

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

#pod =method before_build
#pod
#pod The method checks validity of source and metainfo provider names.
#pod
#pod =cut

sub before_build {
    my ( $self ) = @_;
    if ( $self->strict >= 0 ) {
        my $log_warning = $self->strict > 0 ? 'log_error' : 'log';
        my $zilla = $self->zilla;
        for my $provider ( @{ $self->source_list }, @{ $self->metainfo_list } ) {
            if ( $self->_does_role( $provider, 'Plugin' ) ) {
                if ( $self->_does_role( $provider, 'FileInjector' ) ) {
                    # ok.
                } else {
                    #   It is a plugin but not a `FileInjector`.
                    $self->log_error( [ '%s is not a FileInjector', $provider ] );
                };
            } else {
                #   It is not a plugin or plugin, but not loaded.
                $self->$log_warning( [ '%s is not a loaded plugin', $provider ] );
            };
        };
        $self->abort_if_error();
    };
    return;
};

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

#pod =method gather_files
#pod
#pod This is the main method of the class. It adds a file with name C<< $self->manifest >> to the
#pod distribution. File content is specified by C<CodeRef> to postpone actual file creation. Being
#pod evaluated, the code iterates through all the files in distribution in alphabetical order, and
#pod fulfills the manifest with filenames and comments.
#pod
#pod =cut

sub gather_files {
    my ( $self, $arg ) = @_;
    my $zilla = $self->zilla;
    my $file = Dist::Zilla::File::FromCode->new( {
        name                => $self->manifest,
        code_return_type    => 'bytes',
        code                => sub {
            my @list;
            #   Process all files in alphbetical order.
            for my $file ( sort( { $a->name cmp $b->name } @{ $zilla->files } ) ) {
                push( @list, {
                    file_name => $self->_file_name( $file ),
                    comment   => $self->_comment( $file )
                } );
            };
            #   Find width of filename column.
            my $width = List::Util::max( map( { length( $_->{ file_name } ) } @list ) );
            #   Output formats.
            my $head = "# This file was generated with %s %s\n";
            my $body = "%*s # %s\n";
            return
                join(
                    '',
                    sprintf( $head, blessed( $self ), $self->VERSION ),
                    map( { sprintf( $body, - $width, $_->{ file_name }, $_->{ comment } ) } @list ),
                );
        },
    } );
    $self->add_file( $file );
    return;
};

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

#pod =method _comment
#pod
#pod     $str = $self->_comment( $file );    # Without leading sharp.
#pod
#pod The method returns comment to be used with the specified file. Comment should not include leading
#pod sharp character (C<#>).
#pod
#pod =cut

sub _comment {
    my ( $self, $file ) = @_;

    #   Get file history.
    my $history = $self->_file_history( $file );

    #   Add plugin names.
    for my $entry ( @$history ) {
        $entry->{ plugin } = $self->_plugin( $entry );
    };

    #   Filter out 'filename set' entries.
    $history = [ grep( { $_->{ action } ne 'filename set' } @$history ) ];

    #   Just in case make sure history is not empty.
    if ( not @$history ) {
        $self->log_error( [ '%s file history is empty', $file->name ] );
    };

    #   Get file deed and breed.
    my $deed  = $self->_file_deed(  $file, $history );
    my $breed = $self->_file_breed( $file, $history );

    my $comment = '';
    $comment .= sprintf(
        "%*s file %s by %s",
        $self->file_deed_width, $deed, $breed, $history->[ 0 ]->{ plugin }
    );
    if ( @$history > 1 ) {
        #   Write series of "modified" actions in compact form — do not repeat "modified by".
        $comment .=
            ' and modified by ' .
            join(
                ', ',
                map( { $history->[ $_ ]->{ plugin } } 1 .. @$history - 1 )
            );
    };
    return $comment;
};

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

#pod =method _file_history
#pod
#pod     $arrayref = $self->_file_history( $file );
#pod
#pod The method parses file's C<added_by> log. Internally, C<added_by> log is a list of strings. Here
#pod are few examples:
#pod
#pod     content added by COPYING (Dist::Zilla::Plugin::GenerateFile line 114)
#pod     filename set by GatherFromManifest (Dist::Zilla::Plugin::GatherFromManifest line 125)
#pod     encoded_content added by GatherFromManifest (Dist::Zilla::Plugin::GatherFromManifest line 126)
#pod     text from coderef added by MetaJSON (Dist::Zilla::Plugin::MetaJSON line 83)
#pod     content set by TemplateFiles (Dist::Zilla::Plugin::TemplateFiles line 35)
#pod     content set by OurPkgVersion (Dist::Zilla::Plugin::OurPkgVersion line 82)
#pod     content set by PodWeaver (Dist::Zilla::Plugin::PodWeaver line 175)
#pod
#pod Thus, each string in C<added_by> log follows the format:
#pod
#pod     <action> by <name> (<package> line <number>)
#pod
#pod The method parses these strings and returns file history in more convenient for further processing
#pod form:
#pod
#pod     [ { action => …, name => …, package => …, line => … }, { … }, … ]
#pod
#pod =cut

sub _file_history {
    my ( $self, $file ) = @_;
    my $added_by = $file->{ added_by };
    my $history = [];
    my $n = 0;
    for my $entry ( @$added_by ) {
        ++ $n;
        if ( $entry =~ m{\A (.*?) \s by \s (.*) \s \( ([a-z_0-9:]+) \s line \s (\d+) \) \z}ix ) {
            my ( $action, $name, $package, $line ) = ( $1, $2, $3, $4 );
            push(
                @$history,
                { action => $action, name => $name, package => $package, line => $line }
            );
        } else {
            $self->log_error( [
                "Cannot parse entry #%d in file %s added_by log:\n%s",
                $n, $file->name,
                join(
                    "\n",
                    map(
                        { ( $_ == $n ? '>>> ' : '    ' ) . $added_by->[ $_ - 1 ] }
                        1 .. @$added_by
                    )
                )
            ] );
        };
    };
    $self->abort_if_error();
    return $history;
};

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

#pod =method _plugin
#pod
#pod     $str = $self->_plugin( $history->[ $n ] );
#pod
#pod The method calculates plugin err… not name, but err… TODO!
#pod
#pod =cut

sub _plugin {
    my ( $self, $entry ) = @_;
    return __plugin_name( $entry->{ package } );
};

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

#pod =method _file_name
#pod
#pod     $str = $self->_file_name( $file );
#pod
#pod Returns filename to be used in manifest. If filename does not include special characters (spaces,
#pod backslashes (C<\>), apostrophes (C<'>)), it is the same as real filename, otherwise special
#pod characters are escaped, and entire filename is enclosed into apostrophes.
#pod
#pod =cut

sub _file_name {
    my ( $self, $file ) = @_;
    my $file_name = $file->name;
    if ( $file_name =~ m{[\ '\\]}x ) {
        $file_name =~ s{([\\'])}{\\$1}gx;
        $file_name = "'" . $file_name . "'";
    };
    return $file_name;
};

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

#pod =method _file_deed
#pod
#pod     $str = $self->_file_deed( $file, $history );    # e. g. '3rd party'.
#pod
#pod Returns user-visible name of file deed.
#pod
#pod =cut

sub _file_deed {
    my ( $self, $file, $history ) = @_;
    my $first = $history->[ 0 ];
    my $deeds = $self->file_deeds;
    my $deed;
    if ( $self->source_hash->{ $first->{ plugin } } ) {
        $deed = $deeds->{ source };
    } elsif ( $self->metainfo_hash->{ $first->{ plugin } } ) {
        $deed = $deeds->{ metainfo };
    } else {
        $deed = $deeds->{ other };
    };
    return $deed;
};

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

#pod =method _file_breed
#pod
#pod     $str = $self->_file_breed( $file, $history );   # 'added' or 'built'.
#pod
#pod Returns file breed, i. e. how the file appeared in the distribution — was it copied from project
#pod sources (C<added>) or generated (C<built>).
#pod
#pod Current implementations checks file object class: if it is a C<Dist::Zilla::File::OnDisk>, the file
#pod C<added> to distribution, otherwise file C<built>.
#pod
#pod =cut

sub _file_breed {
    my ( $self, $file, $history ) = @_;
    my $breed = $file->isa( 'Dist::Zilla::File::OnDisk' ) ? 'added' : 'built';
    return $breed;
};

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

#pod =method mvp_multivalue_args
#pod
#pod This method tells C<Dist::Zilla> that C<source_providers> and C<metainfo_providers> are multi-value
#pod options (i. e. can be specified in F<dist.ini> several times).
#pod
#pod =cut

around mvp_multivalue_args => sub {
    my ( $orig, $self ) = @_;
    return ( $self->$orig(), qw{ source_providers metainfo_providers } );
};

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

sub _does_role {
    my ( $self, $plugin, $role ) = @_;
    my $package = __package_name( $plugin );
    my $ok;
    try {
        $ok = $package->meta->does_role( 'Dist::Zilla::Role::' . $role );
    } catch {
        my $ex = "$_";
        chomp( $ex );
        $self->log_debug( [ 'Can\'t check if plugin %s does role %s:', $plugin, $role ] );
        $self->log_debug( [ '    %s', $ex ] );
        $self->log_debug( [ 'Seems %s is not a plugin or not a loaded module', $plugin ] );
    };
    return $ok;
};

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

__PACKAGE__->meta->make_immutable;

1;

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

#pod =head1 SYNOPSIS
#pod
#pod     package Dist::Zilla::Plugin::Manifest::Write::FileSize;
#pod
#pod     use Moose;
#pod     use namespace::autoclean;
#pod     extends 'Dist::Zilla::Plugin::Manifest::Write';
#pod     our $VERSION = '0.007';
#pod
#pod     #   Overload any method or modify it with all the Moose power, e. g.:
#pod     around _comment => sub {
#pod         my ( $orig, $self, $file ) = @_;
#pod         my $comment = $self->$orig( $file );
#pod         if ( $file->name ne $self->manifest ) { # Avoid infinite recursion.
#pod             $comment .= sprintf( ' (%d bytes)', length( $file->encoded_content ) );
#pod         };
#pod         return $comment;
#pod     };
#pod
#pod     __PACKAGE__->meta->make_immutable;
#pod     1;
#pod
#pod =cut

# doc/what.pod #

#pod =encoding UTF-8
#pod
#pod =head1 WHAT?
#pod
#pod C<Manifest::Write> is a plugin for C<Dist::Zilla>, a replacement for standard plugin C<Manifest>.
#pod C<Manifest::Write> writes annotated F<MANIFEST>: each filename is followed by a comment, explaining
#pod origin of the file, whether it is part of software, meta information, or 3rd party file.
#pod
#pod =cut

# end of file #


# end of file #

__END__

=pod

=encoding UTF-8

=head1 NAME

Dist::Zilla::Plugin::Manifest::Write - Have annotated MANIFEST in your distribution

=head1 VERSION

Version v0.8.1, released on 2015-09-15 21:00 UTC.

=head1 WHAT?

C<Manifest::Write> is a plugin for C<Dist::Zilla>, a replacement for standard plugin C<Manifest>.
C<Manifest::Write> writes annotated F<MANIFEST>: each filename is followed by a comment, explaining
origin of the file, whether it is part of software, meta information, or 3rd party file.

This is C<Dist::Zilla::Plugin::Manifest::Write> module documentation. Read this if you are going to hack or
extend C<Manifest::Write>.

If you want to have annotated MANIFEST in your distribution, read the L<manual|Dist::Zilla::Plugin::Manifest::Write::Manual>. General
topics like getting source, building, installing, bug reporting and some others are covered in the
F<README>.

=head1 SYNOPSIS

    package Dist::Zilla::Plugin::Manifest::Write::FileSize;

    use Moose;
    use namespace::autoclean;
    extends 'Dist::Zilla::Plugin::Manifest::Write';
    our $VERSION = '0.007';

    #   Overload any method or modify it with all the Moose power, e. g.:
    around _comment => sub {
        my ( $orig, $self, $file ) = @_;
        my $comment = $self->$orig( $file );
        if ( $file->name ne $self->manifest ) { # Avoid infinite recursion.
            $comment .= sprintf( ' (%d bytes)', length( $file->encoded_content ) );
        };
        return $comment;
    };

    __PACKAGE__->meta->make_immutable;
    1;

=head1 DESCRIPTION

C<Dist::Zilla::Plugin::Manifest::Write> class plays C<Dist::Zilla::Role::FileGatherer> role. To perform the role, the
class implements C<gather_files> method. Other methods are supporting helpers for this one.

Most of attributes are initialized by builder for easier customization by subclassing. Code is
also divided into small methods for the same purpose.

=head1 CLASS METHODS

=head2 BUILDARGS

The method splits C<source_providers> and C<metainfo_providers> options. It allows user to specify
multiple plugin names in one line in F<dist.ini> file, e. g.:

    [Manifest::Write]
        source_providers = GatherDir GatherDir::Template

=head1 OBJECT ATTRIBUTES

=head2 strict

Strictness of checking source and metainfo provider names: -1 (no checks), 0 (some mistakes are
fatal, some are not), or 1 (all mistakes are fatal).

C<Int>, read-only. Default is 1.

See L<Dist::Zilla::Plugin::Manifets::Write::Manual/"strict">.

=head2 manifest

Name of manifest file to write.

C<Str>, read-only. Default value is C<'MANIFEST'>.

=head2 source_list

Array of source providers.

C<ArrayRef[Str]>, read-only, default value is empty array. Init argument (and configure file
option) name is C<source_providers>.

=head2 metainfo_list

Array of metainfo providers.

C<ArrayRef[Str]>, read-only, default value is empty array. Init argument (and configure file
option) name is C<metainfo_providers>.

=head2 source_hash

Hash of source providers (plugin name => boolean), lazily initialized from C<source_list>.
Introduced to make check "is plugin a source provider?" easier.

C<HashRef[Str]>, read-only, not an init arg.

=head2 metainfo_hash

Like C<source_hash>, but for metainfo providers.

=head2 file_deeds

"File deed" is a file ownership: if file belongs to a project, is a metainfo, or third-party file.

This attribute maps internal file deed identifiers (C<source>, C<metainfo>, C<other>) to
user-visible names used in manifest (project name, C<metainfo>, and C<3rd party> respectively).

C<HashRef>, read-only.

=head2 file_deed_width

Width of file deed column. Computed from file deed visible names.

C<Int>, read-only.

=head1 OBJECT METHODS

=head2 before_build

The method checks validity of source and metainfo provider names.

=head2 gather_files

This is the main method of the class. It adds a file with name C<< $self->manifest >> to the
distribution. File content is specified by C<CodeRef> to postpone actual file creation. Being
evaluated, the code iterates through all the files in distribution in alphabetical order, and
fulfills the manifest with filenames and comments.

=head2 _comment

    $str = $self->_comment( $file );    # Without leading sharp.

The method returns comment to be used with the specified file. Comment should not include leading
sharp character (C<#>).

=head2 _file_history

    $arrayref = $self->_file_history( $file );

The method parses file's C<added_by> log. Internally, C<added_by> log is a list of strings. Here
are few examples:

    content added by COPYING (Dist::Zilla::Plugin::GenerateFile line 114)
    filename set by GatherFromManifest (Dist::Zilla::Plugin::GatherFromManifest line 125)
    encoded_content added by GatherFromManifest (Dist::Zilla::Plugin::GatherFromManifest line 126)
    text from coderef added by MetaJSON (Dist::Zilla::Plugin::MetaJSON line 83)
    content set by TemplateFiles (Dist::Zilla::Plugin::TemplateFiles line 35)
    content set by OurPkgVersion (Dist::Zilla::Plugin::OurPkgVersion line 82)
    content set by PodWeaver (Dist::Zilla::Plugin::PodWeaver line 175)

Thus, each string in C<added_by> log follows the format:

    <action> by <name> (<package> line <number>)

The method parses these strings and returns file history in more convenient for further processing
form:

    [ { action => …, name => …, package => …, line => … }, { … }, … ]

=head2 _plugin

    $str = $self->_plugin( $history->[ $n ] );

The method calculates plugin err… not name, but err… TODO!

=head2 _file_name

    $str = $self->_file_name( $file );

Returns filename to be used in manifest. If filename does not include special characters (spaces,
backslashes (C<\>), apostrophes (C<'>)), it is the same as real filename, otherwise special
characters are escaped, and entire filename is enclosed into apostrophes.

=head2 _file_deed

    $str = $self->_file_deed( $file, $history );    # e. g. '3rd party'.

Returns user-visible name of file deed.

=head2 _file_breed

    $str = $self->_file_breed( $file, $history );   # 'added' or 'built'.

Returns file breed, i. e. how the file appeared in the distribution — was it copied from project
sources (C<added>) or generated (C<built>).

Current implementations checks file object class: if it is a C<Dist::Zilla::File::OnDisk>, the file
C<added> to distribution, otherwise file C<built>.

=head2 mvp_multivalue_args

This method tells C<Dist::Zilla> that C<source_providers> and C<metainfo_providers> are multi-value
options (i. e. can be specified in F<dist.ini> several times).

=head1 FUNCTIONS

I would expect to find these functions in C<Dist::Zilla>. Actually, C<Dist::Zilla::Util> defines
the function C<expand_config_package_name>, but that function "is likely to change or go away" and
there is no reverse transformation.

=head2 __plugin_name

    $str = __plugin_name( 'Dist::Zilla::Plugin::Name' );    # 'Name'
    $str = __plugin_name( $plugin );

The function takes either reference to a plugin object or a string, package name, and returns
C<Dist::Zilla> plugin name: If its package name begins with C<Dist::Zilla::Plugin::>, this common
prefix is dropped, otherwise the package name prepended with C<=>.

=head2 __package_name

    $str = __package_name( 'Name' );  # 'Dist::Zilla::Plugin::Name'
    $str = __package_name( $plugin );

This is operation opposite to C<__plugin_name>. It takes either reference to plugin object, or
string, plugin name, and returns package name.

This function is similar to C<expand_config_package_name> from C<Dist::Zilla::Util>, with minor
difference: this function works with plugins only (not with plugin bundles and stashes), and
accepts also reference to plugin objects.

=head1 SEE ALSO

=over 4

=item L<Dist::Zilla>

=item L<Dist::Zilla::Role>

=item L<Dist::Zilla::Role::Plugin>

=item L<Dist::Zilla::Role::FileInjector>

=item L<Dist::Zilla::Role::FileGatherer>

=item L<Dist::Zilla::Plugin::Manifest>

=item L<Dist::Zilla::Plugin::Manifest::Write::Manual>

=back

=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-Plugin-Manifest-Write.

perl-Dist-Zilla-Plugin-Manifest-Write 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-Plugin-Manifest-Write 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-Plugin-Manifest-Write. If not, see <http://www.gnu.org/licenses/>.

=cut
