Получение всех аргументов, передаваемых подпрограмме в виде строки в Perl

Я пытаюсь написать функцию, которая может принимать все свои аргументы и печатать их в виде строки точно так, как они были введены.

Например, используя следующую функцию:

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

Я хотел бы получить следующую строку внутри функции, отформатированную ТОЧНО, как показано ниже:

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

Я хочу сделать это, чтобы я мог распечатать все аргументы так же, как они были введены для целей отладки/тестирования.


person tjwrona1992    schedule 01.04.2016    source источник
comment
Другой пример: скажем, вы хотели сделать test(time). Простая печать @_ напечатает значение эпохи текущего времени, и очень неясно (на первый взгляд), что вы на самом деле проверяете текущее время.   -  person tjwrona1992    schedule 01.04.2016
comment
Используйте Devel::Trace или просто используйте обычный отладчик и прервите строку, где вызывается функция.   -  person ThisSuitIsBlackNot    schedule 01.04.2016
comment
См. также Debug::Show, PadWalker, Data::Dumper::Names , Data::Dumper::Lazy и Debug::ShowStuff::ShowVar   -  person Håkon Hægland    schedule 01.04.2016
comment
Также можно проанализировать строку, используя PPI, используя информацию из caller. Но это сталкивается с некоторыми другими проблемами, такими как определение имени файла исходного файла. Дополнительную информацию см. в этом обсуждении.   -  person Håkon Hægland    schedule 01.04.2016


Ответы (2)


Perl предоставляет специальные хуки отладки, которые позволяют вам видеть необработанные строки скомпилированных исходных файлов. Вы можете написать собственный отладчик, который печатает исходную строку каждый раз, когда вызывается подпрограмма.

Следующее позволяет вам указать одну или несколько подпрограмм, которые вы хотите сопоставить; каждый раз, когда вызывается соответствующая подпрограмма, печатается соответствующая строка.

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;

Отследить вызовы функций foo и Baz::qux в следующей программе:

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

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

Бежать:

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

Обратите внимание, что это напечатает только первую строку вызова, поэтому оно не будет работать для таких вызовов, как

foo( bar,
     baz );
person ThisSuitIsBlackNot    schedule 01.04.2016
comment
Единственная проблема в том, что я хочу использовать это для написания сценария модульного тестирования. По сути, я хочу, чтобы каждый из моих тестов был назван в честь связанной с ними функции и аргументов, поэтому мне не нужно вручную называть каждый тест. Есть ли способ сделать это без явного запуска Perl в режиме отладки? или, может быть, вызвать режим отладки из самого тестового сценария? - person tjwrona1992; 04.04.2016
comment
@ tjwrona1992 Этот подход потерпит неудачу, если вы хотите запустить несколько тестов для одной и той же функции с одним и тем же вводом. Например, с $foo->connect(); $foo->close(); $foo->close(); вы, вероятно, хотите, чтобы первый close закрыл соединение, но вы можете захотеть, чтобы второй close предупредил. Другое поведение, но в вашей схеме у вас будет одинаковое имя теста для обоих. Имя теста должно быть удобочитаемой строкой, описывающей ожидаемое поведение. Сделайте одолжение будущим программистам сопровождения (включая себя!) и напишите описательные имена тестов. - person ThisSuitIsBlackNot; 04.04.2016

Я знаю, что это, вероятно, не лучшее решение, но оно работает:

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 } @_;
}

Теперь, когда вы запускаете это:

test(time);

Вы получите это как вывод:

тест(время);

person tjwrona1992    schedule 01.04.2016