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

oal*_*ers 3 perl

我理想的是能够做的是扫描一堆隐含导入一堆函数的文件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();
Run Code Online (Sandbox Code Playgroud)

我的脚本输出如下.您会注意到该文档似乎没有被更改:

# 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.
Run Code Online (Sandbox Code Playgroud)

mel*_*ene 6

看看来源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);
        }
        '';
}
Run Code Online (Sandbox Code Playgroud)

"非重要令牌"类似于空白或评论.

您正尝试在顶层插入单个重要标记(在语句之后).这是不允许的.

你必须建立一个完整的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;
Run Code Online (Sandbox Code Playgroud)

但让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;
Run Code Online (Sandbox Code Playgroud)

注意:使用$new_element->remove而不仅仅是$new_element至关重要.您需要$new_element从其旧的包含文档中分离,否则临时PPI::Document实例的销毁将清除所有子元素,包括已添加到的元素$doc.

  • 您可以使用PPI解析器生成语句.`my $ use_doc = PPI :: Document-> new(\"使用Test :: Differences;"); 我的$ include =($ doc2-> children)[0];` (2认同)