#   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
#   file: Dist/Zilla/Role/ErrorLogger.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-ErrorLogger.
#pod
#pod perl-Dist-Zilla-Role-ErrorLogger 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-ErrorLogger is distributed in the hope that it will be useful, but WITHOUT
#pod ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
#pod 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-ErrorLogger. If not, see <http://www.gnu.org/licenses/>.
#pod
#pod =cut

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

#pod =for :this This is C<Dist::Zilla::Role::ErrorLogger> role documentation. Read this if you want to
#pod have error logging capabilities in your Dist::Zilla plugin.
#pod
#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::ErrorLogger';
#pod
#pod     sub method {
#pod         my $self = shift( @_ );
#pod         …
#pod         if ( … ) { $self->log_error( 'error message' ); };
#pod         do_something or $self->log_error( 'another error message' );
#pod         …
#pod         while ( … ) {
#pod             do_something_else or $self->log_error( 'error message' ) and next;
#pod         };
#pod         …
#pod         $self->abort_if_error( 'errors found' );
#pod     };
#pod
#pod     __PACKAGE__->meta->make_immutable;
#pod     1;
#pod
#pod =cut

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

#pod =head1 DESCRIPTION
#pod
#pod TODO
#pod
#pod The role requires C<log> and C<log_fatal> methods in the consumer.
#pod
#pod =cut

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

package Dist::Zilla::Role::ErrorLogger;

use Moose::Role;
use feature qw{ state };
use namespace::autoclean;

# ABSTRACT: Have error logging capabilities in your Dist::Zilla plugin
our $VERSION = '0.004'; # VERSION

requires qw{ log log_fatal };

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

#pod =attr error_count
#pod
#pod     $int = $self->error_count;
#pod
#pod Int, read-only. Number of logged errors (i. e. number of made C<log_error> calls).
#pod
#pod =cut

has error_count => (
    is          => 'ro',
    isa         => 'Int',
    default     => 0,
    init_arg    => undef,
);

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

#pod =method log_error
#pod
#pod     $self->log_error( @items );
#pod     $self->log_error( \%args, @items );
#pod
#pod This method calls C<log> method, passing all the arguments, and increments value of C<error_count>
#pod attribute. The method returns true value, so can be used in following constructs:
#pod
#pod     while ( … ) {
#pod         do_something or $self->log_error( 'message' ) and next;
#pod     };
#pod
#pod =cut

sub log_error {
    my $self = shift( @_ );
    #   If the first argument is a hashref, it is treated as extra arguments to logging, not as
    #   message, see <https://metacpan.org/pod/Log::Dispatchouli#log>. Let us add `level` argument.
    #   However, we should not change user-supplied hash and arguments.
    #   ATTENTION:
    #       If we specify level, the test passes but messages do not appear in `dzil` output. So,
    #       level is (temporary?) commented out.
    #   TODO: Investigate the problem.
    state $level = {
        #~ level => 'error',
    };
    my $arg;
    if ( ref( $_[ 0 ] ) eq 'HASH' ) {
        #   User provided hashref, remove it from `@_`.
        $arg = shift( @_ );
        #   Do not override user-supplied `level` argument, if any.
        if ( not exists( $arg->{ level } ) ) {
            #   User did not provide `level` argument, so we need to add it.
            #   Note: new hash created intentionally, to keep user-supplied hash intact.
            $arg = { %$level, %$arg };
        };
    } else {
        #   User did not provide hashref, just use ours.
        $arg = $level;
    };
    $self->log( $arg, @_ );
    ++ $self->{ error_count };
    return $self->{ error_count };
};

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

#pod =method abort
#pod
#pod     $self->abort( @items );
#pod     $self->abort( \%args, @items );
#pod
#pod This method logs all the arguments and aborts C<Dist::Zilla>. Does it look similar to C<log_fatal>?
#pod Yep. This is attempt to workaround L<C<log_fatal>
#pod drawback|https://github.com/rjbs/Dist-Zilla/issues/397>. In contrast to C<log_fatal>, C<abort>
#pod guarantees the message appears on the screen only once.
#pod
#pod TODO: Describe exception.
#pod
#pod =cut

sub abort {
    my $self = shift( @_ );
    if ( @_ ) {
        $self->log( @_ );
    };
    #~ $self->log_fatal( "Aborting..." );
    die( "Aborting...\n" );
};

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

#pod =method abort_if_error
#pod
#pod     $self->abort_if_error( @items );
#pod     $self->abort_if_error( \%args, @items );
#pod
#pod If there was any errors (i. e. C<error_count> is greater than zero), the logs all the arguments and
#pod aborts execution. Both actions (logging and aborting) are implemented by calling C<abort>.
#pod
#pod =cut

sub abort_if_error {
    my $self = shift( @_ );
    if ( $self->error_count ) {
        $self->abort( @_  );
    };
};

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

1;

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

#pod =head1 NOTES
#pod
#pod All the methods defined in the role log items through the C<log> method. C<Dist::Zilla> takes this
#pod method from C<Log::Dispatchouli>, the latter uses C<String::Flogger> to process the messages. It
#pod means you can use C<String::Flogger> tricks, e. g.:
#pod
#pod     $self->log_error( [ 'oops at %s line %d', $file, $line ] );
#pod         #   [] are shorter than sprintf.
#pod
#pod Also note how C<Log::Dispatchouli> describes the C<log> method:
#pod
#pod     $logger->log( @messages );
#pod
#pod and says:
#pod
#pod     Each message is flogged individually, then joined with spaces.
#pod
#pod So beware. A call
#pod
#pod     $self->log_error( 'error 1', 'error 2' );
#pod
#pod logs I<one message> "error 1 error 2", I<not> two messages "error 1" and "error 2", and bumps
#pod C<error_count> by 1, not 2.
#pod
#pod =head1 SEE ALSO
#pod
#pod L<Dist::Zilla>
#pod L<Dist::Zilla::Role>
#pod L<Dist::Zilla::Plugin>
#pod L<Log::Dispatchouli>
#pod L<String::Flogger>
#pod
#pod =cut

# doc/what.pod #

#pod =encoding UTF-8
#pod
#pod =head1 WHAT?
#pod
#pod C<Dist-Zilla-Role-ErrorLogger> is a C<Dist::Zilla> role. It provides C<log_error>, C<abort>, and
#pod C<abort_if_error> methods to consuming plugin.
#pod
#pod =cut

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

#pod =encoding UTF-8
#pod
#pod =head1 WHY?
#pod
#pod C<Dist::Zilla> limits logging capabilities with 3 logging levels available in plugins through
#pod C<log_debug>, C<log>, and C<log_fatal> methods. Debug level messages are turned off by default, the
#pod first fatal message terminates C<Dist::Zilla>. This is simple, but sometimes you may want to report
#pod all the errors, instead of stopping at the first found one. In such a case C<log_fatal> cannot be
#pod used, obviously. There are few alternatives:
#pod
#pod Collect error messages in an array, then report all the errors with single C<log_fatal> call:
#pod
#pod     my @errors;
#pod     …
#pod     push( @errors, … );
#pod     …
#pod     if ( @errors ) {
#pod         $self->log_fatal( join( "\n", @errors ) );
#pod     };
#pod
#pod This works, but current implementation of C<log_fatal> has a disadvantage: it prints the message
#pod twice, so output will looks ugly. (See L<message handling in log_fatal is
#pod suboptimal|https://github.com/rjbs/Dist-Zilla/issues/397>.)
#pod
#pod Another approach is reporting each error immediately with C<log>, counting number of reported
#pod errors, and calling C<log_fatal> once at the end:
#pod
#pod     my $error_count = 0;
#pod     …
#pod     $self->log( 'error' );
#pod     ++ $error_count;
#pod     …
#pod     if ( $error_count ) {
#pod         $self->log_fatal( 'Aborting...' );
#pod     };
#pod
#pod This works, but incrementing the counter after each C<log> call is boring and error-prone.
#pod C<Dist-Zilla-Role-ErrorLogger> role automates it, making plugin code shorter and more readable:
#pod
#pod     with 'Dist-Zilla-Role-ErrorLogger';
#pod     …
#pod     $self->log_error( 'error' );
#pod     …
#pod     $self->abort_if_error();
#pod
#pod =cut

# end of file #


# end of file #

__END__

=pod

=encoding UTF-8

=head1 NAME

Dist::Zilla::Role::ErrorLogger - Have error logging capabilities in your Dist::Zilla plugin

=head1 VERSION

Version 0.004, released on 2015-07-14 18:48 UTC.

=head1 WHAT?

C<Dist-Zilla-Role-ErrorLogger> is a C<Dist::Zilla> role. It provides C<log_error>, C<abort>, and
C<abort_if_error> methods to consuming plugin.

This is C<Dist::Zilla::Role::ErrorLogger> role documentation. Read this if you want to
have error logging capabilities in your Dist::Zilla plugin.

=head1 SYNOPSIS

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

    sub method {
        my $self = shift( @_ );
        …
        if ( … ) { $self->log_error( 'error message' ); };
        do_something or $self->log_error( 'another error message' );
        …
        while ( … ) {
            do_something_else or $self->log_error( 'error message' ) and next;
        };
        …
        $self->abort_if_error( 'errors found' );
    };

    __PACKAGE__->meta->make_immutable;
    1;

=head1 DESCRIPTION

TODO

The role requires C<log> and C<log_fatal> methods in the consumer.

=head1 OBJECT ATTRIBUTES

=head2 error_count

    $int = $self->error_count;

Int, read-only. Number of logged errors (i. e. number of made C<log_error> calls).

=head1 OBJECT METHODS

=head2 log_error

    $self->log_error( @items );
    $self->log_error( \%args, @items );

This method calls C<log> method, passing all the arguments, and increments value of C<error_count>
attribute. The method returns true value, so can be used in following constructs:

    while ( … ) {
        do_something or $self->log_error( 'message' ) and next;
    };

=head2 abort

    $self->abort( @items );
    $self->abort( \%args, @items );

This method logs all the arguments and aborts C<Dist::Zilla>. Does it look similar to C<log_fatal>?
Yep. This is attempt to workaround L<C<log_fatal>
drawback|https://github.com/rjbs/Dist-Zilla/issues/397>. In contrast to C<log_fatal>, C<abort>
guarantees the message appears on the screen only once.

TODO: Describe exception.

=head2 abort_if_error

    $self->abort_if_error( @items );
    $self->abort_if_error( \%args, @items );

If there was any errors (i. e. C<error_count> is greater than zero), the logs all the arguments and
aborts execution. Both actions (logging and aborting) are implemented by calling C<abort>.

=head1 NOTES

All the methods defined in the role log items through the C<log> method. C<Dist::Zilla> takes this
method from C<Log::Dispatchouli>, the latter uses C<String::Flogger> to process the messages. It
means you can use C<String::Flogger> tricks, e. g.:

    $self->log_error( [ 'oops at %s line %d', $file, $line ] );
        #   [] are shorter than sprintf.

Also note how C<Log::Dispatchouli> describes the C<log> method:

    $logger->log( @messages );

and says:

    Each message is flogged individually, then joined with spaces.

So beware. A call

    $self->log_error( 'error 1', 'error 2' );

logs I<one message> "error 1 error 2", I<not> two messages "error 1" and "error 2", and bumps
C<error_count> by 1, not 2.

=head1 WHY?

C<Dist::Zilla> limits logging capabilities with 3 logging levels available in plugins through
C<log_debug>, C<log>, and C<log_fatal> methods. Debug level messages are turned off by default, the
first fatal message terminates C<Dist::Zilla>. This is simple, but sometimes you may want to report
all the errors, instead of stopping at the first found one. In such a case C<log_fatal> cannot be
used, obviously. There are few alternatives:

Collect error messages in an array, then report all the errors with single C<log_fatal> call:

    my @errors;
    …
    push( @errors, … );
    …
    if ( @errors ) {
        $self->log_fatal( join( "\n", @errors ) );
    };

This works, but current implementation of C<log_fatal> has a disadvantage: it prints the message
twice, so output will looks ugly. (See L<message handling in log_fatal is
suboptimal|https://github.com/rjbs/Dist-Zilla/issues/397>.)

Another approach is reporting each error immediately with C<log>, counting number of reported
errors, and calling C<log_fatal> once at the end:

    my $error_count = 0;
    …
    $self->log( 'error' );
    ++ $error_count;
    …
    if ( $error_count ) {
        $self->log_fatal( 'Aborting...' );
    };

This works, but incrementing the counter after each C<log> call is boring and error-prone.
C<Dist-Zilla-Role-ErrorLogger> role automates it, making plugin code shorter and more readable:

    with 'Dist-Zilla-Role-ErrorLogger';
    …
    $self->log_error( 'error' );
    …
    $self->abort_if_error();

=head1 SEE ALSO

L<Dist::Zilla>
L<Dist::Zilla::Role>
L<Dist::Zilla::Plugin>
L<Log::Dispatchouli>
L<String::Flogger>

=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-ErrorLogger.

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

=cut
