The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package TB2::AssertRecord;

use TB2::Mouse;

use Carp;

our $VERSION = '1.005000_005';
$VERSION = eval $VERSION;    ## no critic (BuiltinFunctions::ProhibitStringyEval)


=head1 NAME

TB2::AssertRecord - Record an assert happening

=head1 SYNOPSIS

    use TB2::AssertRecord;

    my $record = TB2::AssertRecord->new;
    my $record = TB2::AssertRecord->new_from_caller($level);

    # All the stuff from caller
    my $package  = $record->package;
    my $filename = $record->filename;
    my $line     = $record->line;


=head1 DESCRIPTION

Records where an assert was called.

Useful for diagnostics and stack traces.

=head1 Constructors

=head2 new

    my $record = TB2::AssertRecord->new({
        package    => $package,
        line       => $line,
        filename   => $filename,
        subroutine => $subroutine
    });

The normal Mouse constructor.  You must supply all the caller
information manually.

You should use C<new_from_caller>.

=head2 new_from_caller

    my $record = TB2::AssertRecord->new_from_caller($level);

Constructs an AssertRecord for you by calling caller() at the given
$level above your call in the call stack.

=cut

sub new_from_caller {
    my $class = shift;
    my $level = shift;

    croak "new_from_caller() must be supplied a level" unless defined $level;

    my @caller = caller($level + 1);

    return $class->new(
        package    => $caller[0],
        filename   => $caller[1],
        line       => $caller[2],
        subroutine => $caller[3],
    );
}


=head2 new_from_guess

    my $record = TB2::AssertRecord->new_from_guess;
    my $record = TB2::AssertRecord->new_from_guess(@ignore_packages);

Constructs an AssertRecord for you by looking up the call stack until
it is out of the calling class.

If @ignore_packages is given, those are also to be ignored when looking
up the stack.

=cut

sub new_from_guess {
    my $class = shift;
    my %ignore = map { $_ => 1 } @_;

    $ignore{ caller() } = 1;

    my @last_caller;
    my $height = 0;
    while(1) {
        my @caller = caller($height++);

        last if !@caller;               # walked off the stack

        @last_caller = @caller;

        last unless $ignore{$caller[0]};
    } 

    return $class->new(
        package    => $last_caller[0],
        filename   => $last_caller[1],
        line       => $last_caller[2],
        subroutine => $last_caller[3],
    );
}


=head1 Accessors

These are all read-only and act in the expected manner.

=head2 filename

The filename from which the asset was called.

=head2 line

The line where the assert was called.

=head2 package

The class or package in which the assert was called.

=head2 subroutine

The fully qualified name of the assert being called.

=cut

has filename =>
  is            => 'ro',
  isa           => 'Str',
  required      => 1,
;

has line =>
  is            => 'ro',
  isa           => 'Int',
  required      => 1,
;

has package =>
  is            => 'ro',
  isa           => 'Str',  # ClassName requires the class exist which is too restrictive
  required      => 1,
;

has subroutine =>
  is            => 'ro',
  isa           => 'Str',
;

no TB2::Mouse;

1;