Как добавить новые строки в Perl-скрипт, используя PPI? - PullRequest
0 голосов
/ 16 ноября 2018

В идеале я хотел бы иметь возможность сканировать группу файлов, которые неявным образом импортируют группу функций через Test::Most.Я хотел бы явно импортировать функции в файле.Поэтому в основном я проверю операторы use, чтобы увидеть, существуют ли они уже, и, если их нет, я бы хотел добавить дополнительный оператор использования для рассматриваемых функций.Например, я мог бы добавить use Test::Differences qw( eq_or_diff );, если в файле есть eq_or_diff, но нет use Test::Differences.Это будет немного сложнее, но это основная идея.

В качестве подтверждения концепции я попытался добавить всего одно слово в существующий скрипт, но не могу понять,insert_after() возвращает true в случае успеха.Я только когда-либо получаю значение false, но я не вижу отладочной информации о том, почему нельзя добавить строку.

use strict;
use warnings;

use PPI::Document ();
use PPI::Token::Word ();
use Test::More;

my $script = <<'EOF';
use strict;
use warnings;

use DateTime ();
use Git::Helpers qw( checkout_root );
use LWP::UserAgent ();

my $foo = 'bar';
EOF

my $doc = PPI::Document->new( \$script );

my $includes    = $doc->find('PPI::Statement::Include');
my @use         = grep { $_->type eq 'use' } @{$includes};
my $second_last = $use[-2];

diag 'Trying to insert after ' . $second_last->module;

my $word = PPI::Token::Word->new('use');
isa_ok( $word,        'PPI::Element', 'word is an Element' );
isa_ok( $second_last, 'PPI::Element', 'use is an Element' );

ok( $second_last->insert_after($word), 'word inserted' );

diag $doc->serialize;

done_testing();

Вывод моего сценария выглядит следующим образом.Вы заметите, что документ не был изменен:

# trying to insert after Git::Helpers
ok 1 - 'word is an Element' isa 'PPI::Element'
ok 2 - 'use is an Element' isa 'PPI::Element'
not ok 3 - word inserted
#   failed test 'word inserted'
#   at so.pl line 31.
# use strict;
# use warnings;
#
# use DateTime ();
# use Git::Helpers qw( checkout_root );
# use LWP::UserAgent ();
#
# my $foo = 'bar';
1..3
# looks like you failed 1 test of 3.

1 Ответ

0 голосов
/ 16 ноября 2018

Глядя на источник PPI::Statement:

# As above, you can insert a statement, or a non-significant token
sub insert_after {
        my $self    = shift;
        my $Element = _INSTANCE(shift, 'PPI::Element') or return undef;
        if ( $Element->isa('PPI::Statement') ) {
                return $self->__insert_after($Element);
        } elsif ( $Element->isa('PPI::Token') and ! $Element->significant ) {
                return $self->__insert_after($Element);
        }
        '';
}

"Незначительный токен" - это что-то вроде пробела или комментария.

Выпопытка вставить один значимый токен на верхнем уровне (после оператора).Это не разрешено.

Вам нужно будет создать полный PPI::Statement::Include элемент.


Вот некоторый (довольно некрасивый) код подтверждения концепции:

# ...
diag 'Trying to insert after ' . $second_last->module;

{
    my $insertion_point = $second_last;
    for my $new_element (
        do {
            my $synthetic_use = PPI::Statement::Include->new;
            for my $child (
                PPI::Token::Word->new('use'),
                PPI::Token::Whitespace->new(' '),
                PPI::Token::Word->new('Test::Differences'),
                PPI::Token::Whitespace->new(' '),
                PPI::Token::Quote::Single->new("'eq_or_diff'"),
                PPI::Token::Structure->new(';'),
            ) {
                ok $synthetic_use->add_element($child);
            }
            $synthetic_use
        },
        PPI::Token::Whitespace->new("\n"),
    ) {
        ok $insertion_point->insert_after($new_element);
    }
}

diag $doc->serialize;

Но гораздо проще позволить PPI проанализировать данный фрагмент и просто использовать эти объекты:

diag 'Trying to insert after ' . $second_last->module;

{
    my $insertion_point = $second_last;
    for my $new_element (
        reverse PPI::Document->new(\ "\nuse Test::Differences qw( eq_or_diff );")->elements
    ) {
        ok $insertion_point->insert_after($new_element->remove);
    }
}

diag $doc->serialize;

Осторожно: крайне важно использовать $new_element->remove вместо $new_element.Вам необходимо отсоединить $new_element от старого содержащего его документа, поскольку в противном случае временный экземпляр PPI::Document уничтожит все дочерние элементы, включая те, которые уже добавлены в $doc.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...