如何使用PPI向Perl脚本添加新行?

时间:2018-11-15 21:55:45

标签: perl

我理想地想要做的是扫描一堆文件,这些文件通过Test::Most隐式地导入了一堆函数。我想在文件中显式导入函数。因此,基本上,我将检查use语句以查看它们是否已经存在,如果不存在,我想为所涉及的函数添加一个附加的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 :(得分:6)

source of 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的子元素。