#   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
#   file: lib/Dist/Zilla/Role/TextTemplater.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-Role-TextTemplater.
#pod
#pod perl-Dist-Zilla-Role-TextTemplater is free software: you can redistribute it and/or modify it
#pod under the terms of the GNU General Public License as published by the Free Software Foundation,
#pod either version 3 of the License, or (at your option) any later version.
#pod
#pod perl-Dist-Zilla-Role-TextTemplater 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-Role-TextTemplater. If not, see <http://www.gnu.org/licenses/>.
#pod
#pod =cut

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

#pod =for :this This is C<Dist::Zilla::Role::TextTemplater> module documentation. Read this if you want to
#pod have text templating capabilities in your Dist::Zilla plugin.
#pod
#pod =for :that If you are using a C<TextTemplater>-based plugin, read the
#pod L<manual|Dist::Zilla::Role::TextTemplater::Manual>. General topics like getting source, building, installing, bug
#pod reporting and some others are covered in the L<readme|Dist::Zilla::Role::TextTemplater::ReadMe>.
#pod
#pod =head1 SYNOPSIS
#pod
#pod     package Dist::Zilla::Plugin::YourPlugin;
#pod     use Moose;
#pod     use namespace::autoclean;
#pod     with 'Dist::Zilla::Role::Plugin';
#pod     with 'Dist::Zilla::Role::TextTemplater';
#pod
#pod     sub method {
#pod         my $self = shift( @_ );
#pod         …
#pod         $result = $self->fill_in_string( $template );
#pod         …
#pod     };
#pod
#pod     __PACKAGE__->meta->make_immutable;
#pod     1;
#pod
#pod =head1 DESCRIPTION
#pod
#pod The role provides consuming plugin with C<fill_in_string> method and bunch of accompanying
#pod attributes and F<dist.ini> options.
#pod
#pod =cut

package Dist::Zilla::Role::TextTemplater;

use Moose::Role;
use namespace::autoclean;

# ABSTRACT: Have text templating capabilities in your Dist::Zilla plugin
our $VERSION = '0.003'; # VERSION

with 'Dist::Zilla::Role::ErrorLogger' => { -version => 0.005 };

use Text::Template qw{};
use Carp qw{ croak };

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

#pod =attr delimiters
#pod
#pod Pair of open and closing delimiters the code fragments surrounded by.
#pod
#pod Attribute introduces F<dist.ini> option with the same name. Option value will be split on
#pod whitespaces (and result should ) to initialize the attribute.
#pod
#pod C<Str|ArrayRef[Str]>, read-only. Default value is C<[ '{{', '}}' ]>.
#pod
#pod See C<DELIMITERS> option of L<Text::Template/"fill_in">.
#pod
#pod Note: Our default delimiters are "alternative delimiters" for C<Text::Template>. It means escaping
#pod delimiters with backslash does not work. See L<Text::Template/"Alternative Delimiters">.
#pod
#pod =cut

has delimiters => (
    is          => 'ro',
    isa         => 'Str|ArrayRef[Str]',
    lazy        => 1,
    default     => sub { [ '{{', '}}' ] },
    trigger     => sub {
        my ( $self, $new ) = @_;
        if ( not ref( $new ) ) {
            $new =~ s{\A\s+}{};     # Drop leading ws, or `split` may leave the first item empty.
            $new = [ split( qr{\s+}, $new ) ];
            @$new == 2
                or croak "\"delimiters\" value must be Str of *two* whitespace-separated words";
            $self->{ delimiters } = $new;
        } else {
            @$new == 2
                or croak "\"delimiters\" value must be ArrayRef with *two* elements";
        };
    },
);

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

#pod =attr package
#pod
#pod Name of package to evaluate code fragments in.
#pod
#pod Attribute introduces F<dist.ini> option with the same name.
#pod
#pod Str, read-only, optional.
#pod
#pod See C<PACKAGE> option of L<Text::Template/"fill_in">.
#pod
#pod =cut

has package => (
    is          => 'ro',
    isa         => 'Str',
    init_arg    => 'package',
);

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

#pod =attr prepend
#pod
#pod Perl code to prepend to the beginning of every code fragment.
#pod
#pod Attribute introduces F<dist.ini> multi-value option with the same name.
#pod
#pod ArrayRef[Str], read-only, auto dereferenced. Default value is empty array. Another default value
#pod may be specified by defining C<_build_prepend> method.
#pod
#pod See C<PREPEND> option of L<Text::Template/"fill_in">.
#pod
#pod =cut

has prepend => (
    is          => 'ro',
    isa         => 'ArrayRef[Str]',
    builder     => '_build_prepend',
    auto_deref  => 1,
);

sub _build_prepend {
    return [];
};

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

#pod =attr broken
#pod
#pod Callback to execute if a code fragment dies. Default callback formats detailed error message and
#pod log it by calling C<log_error>.
#pod
#pod CodeRef, read-only, not an init argument. Default callback may be overridden by defining
#pod C<_build_broken> method.
#pod
#pod See C<BROKEN> option of L<Text::Template/"fill_in">.
#pod
#pod =cut

has broken => (
    is          => 'ro',
    isa         => 'CodeRef',
    builder     => '_build_broken',
    init_arg    => undef,
);

sub _build_broken {
    my ( $self ) = @_;
    return sub {
        my %args = @_;
        my $error    = $args{ error  }; # Error message.
        my $bline    = $args{ lineno }; # Template line where the problem code fragment begins.
        my $eline    = $error =~ m{ at template line (\d+)} ? $1 : 0;
            #   ^Template line where the problem occurs.
        my @template = split( "\n", $args{ arg } );
            #   ^Template text.
        chomp( $error );
        $self->log_error( $error );
        $self->log_error( "Problem code fragment begins at template line $bline." );
        $self->log_error( "Template text:" );
        #   TODO: Chop too long lines.
        #   TODO: Do not show too big templates, show only problem lines.
        my $format = '%s %0' . length( @template + 0 ) . 'd: %s';
        my $n = 0;
        for my $line ( @template ) {
            ++ $n;
            $self->log_error( [ $format, ( $n == $eline ? '>>>' : '   ' ), $n, $line ] );
        };
        # TODO: Do we need $self->abort() here?
    };
};

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

#pod =method mvp_multivalue_args
#pod
#pod The method tells C<Dist::Zilla> that C<prepend> is a multi-value option.
#pod
#pod =cut

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

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

#pod =method fill_in_string
#pod
#pod     $result = $self->fill_in_string( $template, \%variables, \%extra_args );
#pod     $result = $self->fill_in_string( $template );
#pod
#pod The primary method of the role.
#pod
#pod The interface is compatible with method of C<TextTemplate> role, so this role can be used as a
#pod drop-in replacement for C<TextTemplate>. However, methods are implemented slightly differently
#pod (this method calls C<Text::Template::fill_in_string> function, that one creates C<Text::Template>
#pod object first, then calls C<fill_in> method on the object), it may cause subtle difference in
#pod behaviour.
#pod
#pod Template variables C<$plugin> and C<$dist> are set by default, if not explicitly defined in
#pod C<\%variables>.
#pod
#pod The method calls C<Text::Template::fill_in_string>, passing to it the C<$template>, C<\%variables>,
#pod attribute values C<package>, C<prepend>, C<broken>, and finally C<%extra_args>. C<%extra_args> is
#pod passed in the end intentionally, so caller may override any of parameters specified by the method,
#pod for example:
#pod
#pod     $self->fill_in_string( $template, undef, { package => 'MY' } );
#pod         #   Execute code fragments in package MY regardless of $self->package value.
#pod
#pod It also allows to cancel defining C<$plugin> and C<$dist> variables, e. g.:
#pod
#pod     $self->fill_in_string( $template, undef, { hash => {} } );
#pod         #   Do not define any variables for the template.
#pod
#pod =cut

sub fill_in_string {
    my ( $self, $string, $hash, $args ) = @_;
    my %hash = (
        plugin => \( $self ),
        dist   => \( $self->zilla ),
        $hash ? %$hash : (),
    );
    my @args = (
        hash       => \%hash,
        delimiters => $self->delimiters,
        $self->package ? ( package => $self->package,              ) : (),
        $self->prepend ? ( prepend => join( "\n", $self->prepend ) ) : (),
        $self->broken  ? (
            broken      => $self->broken,
            broken_arg  => $string,
        ) : (),
        $args ? %$args : (),
    );
    my $result;
    {
        #   TODO: Create an attribute.
        local $SIG{ __WARN__ } = sub {
            my ( $msg ) = @_;
            chomp( $msg );
            $self->log( $msg );
        };
        $result = Text::Template::fill_in_string( $string, @args );
    }
    if ( not defined( $result ) ) {
        $self->log_error( $Text::Template::ERROR );
    };
    $self->abort_if_error();
    return $result;
};

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

1;

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

#pod =head1 NOTES
#pod
#pod =head2 C<Text::Template> option spelling
#pod
#pod C<Text::Template> allows a programmer to use different spelling of options: all-caps, first-caps,
#pod all-lowercase, with and without leading dash, e. g.: C<HASH>, C<Hash>, C<hash>, C<-HASH>, C<-Hash>,
#pod C<-hash>. This is documented feature.
#pod
#pod C<Text::Template> recommends to pick a style and stick with it. (BTW, C<Text::Template>
#pod documentation itself uses all-caps spelling.) This role picked all-lowercase style. This choice
#pod have subtle consequences. Let us consider an example:
#pod
#pod     $self->fill_in_string( $template, undef, { PACKAGE => 'MY' } );
#pod
#pod Extra option C<PACKAGE> may or may not not have effect, depending on value of C<package> attribute
#pod (i. e. presence or absence C<package> option in F<dist.ini> file), because (this is not documented)
#pod spellings are not equal: different spellings have different priority. If C<PACKAGE> and C<package>
#pod are specified simultaneously, C<package> wins, C<PACKAGE> loses.
#pod
#pod This feature gives you a choice. If you want to ignore option specified by the user in F<dist.ini>
#pod and provide your value, use all-lowercase option name. If you want to provide default which can be
#pod overridden by the user, use all-caps options name.
#pod
#pod =head1 SEE ALSO
#pod
#pod =for :list
#pod = L<Dist::Zilla>
#pod = L<Dist::Zilla::Role>
#pod = L<Dist::Zilla::Plugin>
#pod = L<Dist::Zilla::Role::TextTemplate>
#pod = L<Text::Template>
#pod
#pod =cut

# doc/what.pod #

#pod =encoding UTF-8
#pod
#pod =head1 WHAT?
#pod
#pod C<Dist-Zilla-Role-TextTemplater> is a C<Dist::Zilla> role, a replacement for standard role C<TextTemplate>. Both
#pod roles have the same great C<Text::Template> engine under the hood, but this one provides better
#pod control over the engine and much better error reporting.
#pod
#pod =cut

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

#pod =encoding UTF-8
#pod
#pod =head1 WHY?
#pod
#pod C<TextTemplate> role from C<Dist::Zilla> distribution v5.037 has the same great C<Text::Template>
#pod engine under the hood, but lacks of control and has I<awful> error reporting.
#pod
#pod =head2 Error Reporting
#pod
#pod Let us consider an example. Have a look at F<dist.ini>:
#pod
#pod     name     = Assa
#pod     version  = 0.001
#pod     [GatherDir]
#pod     [PruneCruft]
#pod     [GenerateFile/COPYING]
#pod         filename = COPYING
#pod         content  = This is {{$dist->name}}.
#pod         content  =
#pod         content  = {{$dst->license->notice}}
#pod         is_template = 1
#pod     [TemplateFiles]
#pod         filename = lib/Assa.pm
#pod         filename = lib/Assa/Manual.pod
#pod     [MetaResources::Template]
#pod         license = {{$dist->license->url}}
#pod         …
#pod     …
#pod
#pod (Do you see a typo? Real F<dist.ini> can be much longer.) Now let us build the distribution:
#pod
#pod     $ dzil build
#pod     [DZ] beginning to build Assa
#pod     Can't call method "license" on an undefined value at template line 3.
#pod
#pod Oops. Ok, it is clear what was happened, but where?? The only weak clue is word "template".
#pod However, there are many templates in the project: F<lib/Assa.pm>, F<lib/Assa/Manual.pod>, generated
#pod on-the-fly F<COPYING>, and even resources in F<META.yml> — all this stuff is generated from
#pod templates. Where is the problem? Have a happy bug hunting, dude.
#pod
#pod If these plugins (namely, C<GenerateFile>, C<TemplateFiles> and C<MetaResources::Template>) were
#pod written with C<TextTemplateB<r>> role, result would be:
#pod
#pod     $ dzil build
#pod     [DZ] beginning to build Assa
#pod     [COPYING] Can't call method "license" on an undefined value at template line 3.
#pod     [COPYING] Problem code fragment begins at template line 3.
#pod     [COPYING] Template text:
#pod     [COPYING]     1: This is {{$dist->name}}.
#pod     [COPYING]     2:
#pod     [COPYING] >>> 3: {{$dst->license->notice}}
#pod     Aborting...
#pod
#pod See the difference.
#pod
#pod =head2 Engine Control
#pod
#pod C<TextTemplater> allows the end-user to specify C<package> and C<prepender> engine options from
#pod F<dist.ini> file, while C<TextTemplate> allows to specify C<prepender> only programmatically, and
#pod does I<not> allow to specify C<package>.
#pod
#pod =cut

# end of file #


# end of file #

__END__

=pod

=encoding UTF-8

=head1 NAME

Dist::Zilla::Role::TextTemplater - Have text templating capabilities in your Dist::Zilla plugin

=head1 VERSION

Version 0.003, released on 2015-07-22 12:56 UTC.

=head1 WHAT?

C<Dist-Zilla-Role-TextTemplater> is a C<Dist::Zilla> role, a replacement for standard role C<TextTemplate>. Both
roles have the same great C<Text::Template> engine under the hood, but this one provides better
control over the engine and much better error reporting.

This is C<Dist::Zilla::Role::TextTemplater> module documentation. Read this if you want to
have text templating capabilities in your Dist::Zilla plugin.

If you are using a C<TextTemplater>-based plugin, read the
L<manual|Dist::Zilla::Role::TextTemplater::Manual>. General topics like getting source, building, installing, bug
reporting and some others are covered in the L<readme|Dist::Zilla::Role::TextTemplater::ReadMe>.

=head1 SYNOPSIS

    package Dist::Zilla::Plugin::YourPlugin;
    use Moose;
    use namespace::autoclean;
    with 'Dist::Zilla::Role::Plugin';
    with 'Dist::Zilla::Role::TextTemplater';

    sub method {
        my $self = shift( @_ );
        …
        $result = $self->fill_in_string( $template );
        …
    };

    __PACKAGE__->meta->make_immutable;
    1;

=head1 DESCRIPTION

The role provides consuming plugin with C<fill_in_string> method and bunch of accompanying
attributes and F<dist.ini> options.

=head1 OBJECT ATTRIBUTES

=head2 delimiters

Pair of open and closing delimiters the code fragments surrounded by.

Attribute introduces F<dist.ini> option with the same name. Option value will be split on
whitespaces (and result should ) to initialize the attribute.

C<Str|ArrayRef[Str]>, read-only. Default value is C<[ '{{', '}}' ]>.

See C<DELIMITERS> option of L<Text::Template/"fill_in">.

Note: Our default delimiters are "alternative delimiters" for C<Text::Template>. It means escaping
delimiters with backslash does not work. See L<Text::Template/"Alternative Delimiters">.

=head2 package

Name of package to evaluate code fragments in.

Attribute introduces F<dist.ini> option with the same name.

Str, read-only, optional.

See C<PACKAGE> option of L<Text::Template/"fill_in">.

=head2 prepend

Perl code to prepend to the beginning of every code fragment.

Attribute introduces F<dist.ini> multi-value option with the same name.

ArrayRef[Str], read-only, auto dereferenced. Default value is empty array. Another default value
may be specified by defining C<_build_prepend> method.

See C<PREPEND> option of L<Text::Template/"fill_in">.

=head2 broken

Callback to execute if a code fragment dies. Default callback formats detailed error message and
log it by calling C<log_error>.

CodeRef, read-only, not an init argument. Default callback may be overridden by defining
C<_build_broken> method.

See C<BROKEN> option of L<Text::Template/"fill_in">.

=head1 OBJECT METHODS

=head2 mvp_multivalue_args

The method tells C<Dist::Zilla> that C<prepend> is a multi-value option.

=head2 fill_in_string

    $result = $self->fill_in_string( $template, \%variables, \%extra_args );
    $result = $self->fill_in_string( $template );

The primary method of the role.

The interface is compatible with method of C<TextTemplate> role, so this role can be used as a
drop-in replacement for C<TextTemplate>. However, methods are implemented slightly differently
(this method calls C<Text::Template::fill_in_string> function, that one creates C<Text::Template>
object first, then calls C<fill_in> method on the object), it may cause subtle difference in
behaviour.

Template variables C<$plugin> and C<$dist> are set by default, if not explicitly defined in
C<\%variables>.

The method calls C<Text::Template::fill_in_string>, passing to it the C<$template>, C<\%variables>,
attribute values C<package>, C<prepend>, C<broken>, and finally C<%extra_args>. C<%extra_args> is
passed in the end intentionally, so caller may override any of parameters specified by the method,
for example:

    $self->fill_in_string( $template, undef, { package => 'MY' } );
        #   Execute code fragments in package MY regardless of $self->package value.

It also allows to cancel defining C<$plugin> and C<$dist> variables, e. g.:

    $self->fill_in_string( $template, undef, { hash => {} } );
        #   Do not define any variables for the template.

=head1 NOTES

=head2 C<Text::Template> option spelling

C<Text::Template> allows a programmer to use different spelling of options: all-caps, first-caps,
all-lowercase, with and without leading dash, e. g.: C<HASH>, C<Hash>, C<hash>, C<-HASH>, C<-Hash>,
C<-hash>. This is documented feature.

C<Text::Template> recommends to pick a style and stick with it. (BTW, C<Text::Template>
documentation itself uses all-caps spelling.) This role picked all-lowercase style. This choice
have subtle consequences. Let us consider an example:

    $self->fill_in_string( $template, undef, { PACKAGE => 'MY' } );

Extra option C<PACKAGE> may or may not not have effect, depending on value of C<package> attribute
(i. e. presence or absence C<package> option in F<dist.ini> file), because (this is not documented)
spellings are not equal: different spellings have different priority. If C<PACKAGE> and C<package>
are specified simultaneously, C<package> wins, C<PACKAGE> loses.

This feature gives you a choice. If you want to ignore option specified by the user in F<dist.ini>
and provide your value, use all-lowercase option name. If you want to provide default which can be
overridden by the user, use all-caps options name.

=head1 WHY?

C<TextTemplate> role from C<Dist::Zilla> distribution v5.037 has the same great C<Text::Template>
engine under the hood, but lacks of control and has I<awful> error reporting.

=head2 Error Reporting

Let us consider an example. Have a look at F<dist.ini>:

    name     = Assa
    version  = 0.001
    [GatherDir]
    [PruneCruft]
    [GenerateFile/COPYING]
        filename = COPYING
        content  = This is {{$dist->name}}.
        content  =
        content  = {{$dst->license->notice}}
        is_template = 1
    [TemplateFiles]
        filename = lib/Assa.pm
        filename = lib/Assa/Manual.pod
    [MetaResources::Template]
        license = {{$dist->license->url}}
        …
    …

(Do you see a typo? Real F<dist.ini> can be much longer.) Now let us build the distribution:

    $ dzil build
    [DZ] beginning to build Assa
    Can't call method "license" on an undefined value at template line 3.

Oops. Ok, it is clear what was happened, but where?? The only weak clue is word "template".
However, there are many templates in the project: F<lib/Assa.pm>, F<lib/Assa/Manual.pod>, generated
on-the-fly F<COPYING>, and even resources in F<META.yml> — all this stuff is generated from
templates. Where is the problem? Have a happy bug hunting, dude.

If these plugins (namely, C<GenerateFile>, C<TemplateFiles> and C<MetaResources::Template>) were
written with C<TextTemplateB<r>> role, result would be:

    $ dzil build
    [DZ] beginning to build Assa
    [COPYING] Can't call method "license" on an undefined value at template line 3.
    [COPYING] Problem code fragment begins at template line 3.
    [COPYING] Template text:
    [COPYING]     1: This is {{$dist->name}}.
    [COPYING]     2:
    [COPYING] >>> 3: {{$dst->license->notice}}
    Aborting...

See the difference.

=head2 Engine Control

C<TextTemplater> allows the end-user to specify C<package> and C<prepender> engine options from
F<dist.ini> file, while C<TextTemplate> allows to specify C<prepender> only programmatically, and
does I<not> allow to specify C<package>.

=head1 SEE ALSO

=over 4

=item L<Dist::Zilla>

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

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

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

=item L<Text::Template>

=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-Role-TextTemplater.

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

=cut
