Getting all arguments passed to a subroutine as a string in Perl
Asked Answered
T

2

3

I am trying to write a function that can take all of its arguments and print them as a string exactly as they were entered.

For example using the following function:

test('arg1' => $arg1, 'arg2' => $arg2);

I would like to get the following string inside of the function formatted EXACTLY as seen below:

"'arg1' => $arg1, 'arg2' => $arg2"

I want to do this so I can print all of the arguments the same way that they were entered for debugging/testing purposes.

Theine answered 1/4, 2016 at 16:46 Comment(4)
Another example: say you wanted to do test(time). Simply printing @_ will print the epoch value of the current time and its very unclear (at first glance) that you are actually testing the current time.Theine
Use Devel::Trace, or just use the regular debugger and break at the line where the function is called.Semifinalist
See also Debug::Show, PadWalker, Data::Dumper::Names, Data::Dumper::Lazy and Debug::ShowStuff::ShowVarGrateful
It is also possible to parse the line using PPI using information from caller. But this runs into some other problems like determining the file name of the source file. See this discussion for more informationGrateful
S
6

Perl provides special debugging hooks that let you see the raw lines of compiled source files. You can write a custom debugger that prints the original line every time a subroutine is invoked.

The following lets you specify one or more subroutines you want to match; every time a matching subroutine is invoked, the corresponding line is printed.

package Devel::ShowCalls;

our %targets;

sub import {
    my $self = shift;

    for (@_) {
        # Prepend 'main::' for names without a package specifier
        $_ = "main::$_" unless /::/;
        $targets{$_} = 1;        
    }
}

package DB;

sub DB {
    ($package, $file, $line) = caller;
}

sub sub {
    print ">> $file:$line: ",
          ${ $main::{"_<$file"} }[$line] if $Devel::ShowCalls::targets{$sub};
    &$sub;
}

1;

To trace invocations of functions foo and Baz::qux in the following program:

sub foo {}
sub bar {}
sub Baz::qux {}

foo(now => time);
bar rand;
Baz::qux( qw/unicorn pony waffles/ );

Run:

$ perl -d:ShowCalls=foo,Baz::qux myscript.pl 
>> myscript.pl:5: foo(now => time);
>> myscript.pl:7: Baz::qux( qw/unicorn pony waffles/ );

Note that this will only print the first line of the invocation, so it won't work for calls like

foo( bar,
     baz );
Semifinalist answered 1/4, 2016 at 19:6 Comment(2)
The only problem is I want to use this to write a unit testing script. Essentially I want my tests to each be named after their associated function and arguments that way I don't need to manually name each test. Is there a way to do this without explicitly running Perl in debug mode? or maybe invoke debug mode from within the test script itself?Theine
@tjwrona1992 That approach will fail if you want to run multiple tests on the same function with the same input. For example, with $foo->connect(); $foo->close(); $foo->close();, you probably want the first close to close the connection, but you may want the second close to warn. Different behavior, but in your scheme, you'll have the exact same test name for both. The test name should be a human-readable string describing the expected behavior. Do any future maintenance programmers (including yourself!) a favor and write descriptive test names.Semifinalist
T
0

I know this is probably not the best solution, but it works:

sub test {
    my (undef, $file_name, $line_number) = caller;
    open my $fh, '<', $file_name or die $!;
    my @lines = <$fh>;
    close $fh;

    my $line = $lines[$line_number - 1];
    trim($line);

    print $line."\n";
}

sub trim {
    return map { $_ =~ s/^\s+|\s+$//g } @_;
}

Now when you run this:

test(time);

You will get this as the output:

test(time);

Theine answered 1/4, 2016 at 18:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.