<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">package Data::Dump::Trace;

$VERSION = "0.02";

# Todo:
#   - prototypes
#     in/out parameters key/value style
#   - exception
#   - wrap class
#   - configurable colors
#   - show call depth using indentation
#   - show nested calls sensibly
#   - time calls

use strict;

use base 'Exporter';
our @EXPORT_OK = qw(call mcall wrap autowrap trace);

use Carp qw(croak);
use overload ();

my %obj_name;
my %autowrap_class;
my %name_count;

sub autowrap {
    while (@_) {
        my $class = shift;
        my $info = shift;
        $info = { prefix =&gt; $info } unless ref($info);
        for ($info-&gt;{prefix}) {
            unless ($_) {
                $_ = lc($class);
                s/.*:://;
            }
            $_ = '$' . $_ unless /^\$/;
        }
        $autowrap_class{$class} = $info;
    }
}

sub wrap {
    my %arg = @_;
    my $name = $arg{name} || "func";
    my $func = $arg{func};
    my $proto = $arg{proto};

    return sub {
        call($name, $func, $proto, @_);
    } if $func;

    if (my $obj = $arg{obj}) {
        $name = '$' . $name unless $name =~ /^\$/;
        $obj_name{overload::StrVal($obj)} = $name;
        return bless {
            name =&gt; $name,
            obj =&gt; $obj,
            proto =&gt; $arg{proto},
        }, "Data::Dump::Trace::Wrapper";
    }

    croak("Either the 'func' or 'obj' option must be given");
}

sub trace {
    my($symbol, $prototype) = @_;
    no strict 'refs';
    no warnings 'redefine';
    *{$symbol} = wrap(name =&gt; $symbol, func =&gt; \&amp;{$symbol}, proto =&gt; $prototype);
}

sub call {
    my $name = shift;
    my $func = shift;
    my $proto = shift;
    my $fmt = Data::Dump::Trace::Call-&gt;new($name, $proto, \@_);
    if (!defined wantarray) {
        $func-&gt;(@_);
        return $fmt-&gt;return_void(\@_);
    }
    elsif (wantarray) {
        return $fmt-&gt;return_list(\@_, $func-&gt;(@_));
    }
    else {
        return $fmt-&gt;return_scalar(\@_, scalar $func-&gt;(@_));
    }
}

sub mcall {
    my $o = shift;
    my $method = shift;
    my $proto = shift;
    return if $method eq "DESTROY" &amp;&amp; !$o-&gt;can("DESTROY");
    my $oname = ref($o) ? $obj_name{overload::StrVal($o)} || "\$o" : $o;
    my $fmt = Data::Dump::Trace::Call-&gt;new("$oname-&gt;$method", $proto, \@_);
    if (!defined wantarray) {
        $o-&gt;$method(@_);
        return $fmt-&gt;return_void(\@_);
    }
    elsif (wantarray) {
        return $fmt-&gt;return_list(\@_, $o-&gt;$method(@_));
    }
    else {
        return $fmt-&gt;return_scalar(\@_, scalar $o-&gt;$method(@_));
    }
}

package Data::Dump::Trace::Wrapper;

sub AUTOLOAD {
    my $self = shift;
    our $AUTOLOAD;
    my $method = substr($AUTOLOAD, rindex($AUTOLOAD, '::')+2);
    Data::Dump::Trace::mcall($self-&gt;{obj}, $method, $self-&gt;{proto}{$method}, @_);
}

package Data::Dump::Trace::Call;

use Term::ANSIColor ();
use Data::Dump ();

*_dump = \&amp;Data::Dump::dump;

our %COLOR = (
    name =&gt; "yellow",
    output =&gt; "cyan",
    error =&gt; "red",
    debug =&gt; "red",
);

%COLOR = () unless -t STDOUT;

sub _dumpav {
    return "(" . _dump(@_) . ")" if @_ == 1;
    return _dump(@_);
}

sub _dumpkv {
    return _dumpav(@_) if @_ % 2;
    my %h = @_;
    my $str = _dump(\%h);
    $str =~ s/^\{/(/ &amp;&amp; $str =~ s/\}\z/)/;
    return $str;
}

sub new {
    my($class, $name, $proto, $input_args) = @_;
    my $self = bless {
        name =&gt; $name,
        proto =&gt; $proto,
    }, $class;
    my $proto_arg = $self-&gt;proto_arg;
    if ($proto_arg =~ /o/) {
        for (@$input_args) {
            push(@{$self-&gt;{input_av}}, _dump($_));
        }
    }
    else {
        $self-&gt;{input} = $proto_arg eq "%" ? _dumpkv(@$input_args) : _dumpav(@$input_args);
    }
    return $self;
}

sub proto_arg {
    my $self = shift;
    my($arg, $ret) = split(/\s*=\s*/, $self-&gt;{proto} || "");
    $arg ||= '@';
    return $arg;
}

sub proto_ret {
    my $self = shift;
    my($arg, $ret) = split(/\s*=\s*/, $self-&gt;{proto} || "");
    $ret ||= '@';
    return $ret;
}

sub color {
    my($self, $category, $text) = @_;
    return $text unless $COLOR{$category};
    return Term::ANSIColor::colored($text, $COLOR{$category});
}

sub print_call {
    my $self = shift;
    my $outarg = shift;
    print $self-&gt;color("name", "$self-&gt;{name}");
    if (my $input = $self-&gt;{input}) {
        $input = "" if $input eq "()" &amp;&amp; $self-&gt;{name} =~ /-&gt;/;
        print $self-&gt;color("input", $input);
    }
    else {
        my $proto_arg = $self-&gt;proto_arg;
        print "(";
        my $i = 0;
        for (@{$self-&gt;{input_av}}) {
            print ", " if $i;
            my $proto = substr($proto_arg, 0, 1, "");
            if ($proto ne "o") {
                print $self-&gt;color("input", $_);
            }
            if ($proto eq "o" || $proto eq "O") {
                print " = " if $proto eq "O";
                print $self-&gt;color("output", _dump($outarg-&gt;[$i]));
            }
        }
        continue {
            $i++;
        }
        print ")";
    }
}

sub return_void {
    my $self = shift;
    my $arg = shift;
    $self-&gt;print_call($arg);
    print "\n";
    return;
}

sub return_scalar {
    my $self = shift;
    my $arg = shift;
    $self-&gt;print_call($arg);
    my $s = shift;
    my $name;
    my $proto_ret = $self-&gt;proto_ret;
    my $wrap = $autowrap_class{ref($s)};
    if ($proto_ret =~ /^\$\w+\z/ &amp;&amp; ref($s) &amp;&amp; ref($s) !~ /^(?:ARRAY|HASH|CODE|GLOB)\z/) {
        $name = $proto_ret;
    }
    else {
        $name = $wrap-&gt;{prefix} if $wrap;
    }
    if ($name) {
        $name .= $name_count{$name} if $name_count{$name}++;
        print " = ", $self-&gt;color("output", $name), "\n";
        $s = Data::Dump::Trace::wrap(name =&gt; $name, obj =&gt; $s, proto =&gt; $wrap-&gt;{proto});
    }
    else {
        print " = ", $self-&gt;color("output", _dump($s));
        if (!$s &amp;&amp; $proto_ret =~ /!/ &amp;&amp; $!) {
            print " ", $self-&gt;color("error", errno($!));
        }
        print "\n";
    }
    return $s;
}

sub return_list {
    my $self = shift;
    my $arg = shift;
    $self-&gt;print_call($arg);
    print " = ", $self-&gt;color("output", $self-&gt;proto_ret eq "%" ? _dumpkv(@_) : _dumpav(@_)), "\n";
    return @_;
}

sub errno {
    my $t = "";
    for (keys %!) {
        if ($!{$_}) {
            $t = $_;
            last;
        }
    }
    my $n = int($!);
    return "$t($n) $!";
}

1;

__END__

=head1 NAME

Data::Dump::Trace - Helpers to trace function and method calls

=head1 SYNOPSIS

  use Data::Dump::Trace qw(autowrap mcall);

  autowrap("LWP::UserAgent" =&gt; "ua", "HTTP::Response" =&gt; "res");

  use LWP::UserAgent;
  $ua = mcall(LWP::UserAgent =&gt; "new");      # instead of LWP::UserAgent-&gt;new;
  $ua-&gt;get("http://www.example.com")-&gt;dump;

=head1 DESCRIPTION

The following functions are provided:

=over

=item autowrap( $class )

=item autowrap( $class =&gt; $prefix )

=item autowrap( $class1 =&gt; $prefix1,  $class2 =&gt; $prefix2, ... )

=item autowrap( $class1 =&gt; \%info1, $class2 =&gt; \%info2, ... )

Register classes whose objects are automatically wrapped when
returned by one of the call functions below.  If $prefix is provided
it will be used as to name the objects.

Alternative is to pass an %info hash for each class.  The recognized keys are:

=over

=item prefix =&gt; $string

The prefix string used to name objects of this type.

=item proto =&gt; \%hash

A hash of prototypes to use for the methods when an object is wrapped.

=back

=item wrap( name =&gt; $str, func =&gt; \&amp;func, proto =&gt; $proto )

=item wrap( name =&gt; $str, obj =&gt; $obj, proto =&gt; \%hash )

Returns a wrapped function or object.  When a wrapped function is
invoked then a trace is printed after the underlying function has returned.
When a method on a wrapped object is invoked then a trace is printed
after the methods on the underlying objects has returned.

See L&lt;/"Prototypes"&gt; for description of the C&lt;proto&gt; argument.

=item call( $name, \&amp;func, $proto, @ARGS )

Calls the given function with the given arguments.  The trace will use
$name as the name of the function.

See L&lt;/"Prototypes"&gt; for description of the $proto argument.

=item mcall( $class, $method, $proto, @ARGS )

=item mcall( $object, $method, $proto, @ARGS )

Calls the given method with the given arguments.

See L&lt;/"Prototypes"&gt; for description of the $proto argument.

=item trace( $symbol, $prototype )

Replaces the function given by $symbol with a wrapped function.

=back

=head2 Prototypes

B&lt;Note: The prototype string syntax described here is experimental and
likely to change in revisions of this interface&gt;.

The $proto argument to call() and mcall() can optionally provide a
prototype for the function call.  This give the tracer hints about how
to best format the argument lists and if there are I&lt;in/out&gt; or I&lt;out&gt;
arguments.  The general form for the prototype string is:

   &lt;arguments&gt; = &lt;return_value&gt;

The default prototype is "@ = @"; list of values as input and list of
values as output.

The value '%' can be used for both arguments and return value to say
that key/value pair style lists are used.

Alternatively, individual positional arguments can be listed each
represented by a letter:

=over

=item C&lt;i&gt;

input argument

=item C&lt;o&gt;

output argument

=item C&lt;O&gt;

both input and output argument

=back

If the return value prototype has C&lt;!&gt; appended, then it signals that
this function sets errno ($!) when it returns a false value.  The
trace will display the current value of errno in that case.

If the return value prototype looks like a variable name (with C&lt;$&gt;
prefix), and the function returns a blessed object, then the variable
name will be used as prefix and the returned object automatically
traced.

=head1 SEE ALSO

L&lt;Data::Dump&gt;

=cut
</pre></body></html>