【问题标题】:Sorting module subroutines alphabetically按字母顺序对模块子例程进行排序
【发布时间】:2014-10-22 05:54:32
【问题描述】:

我想按字母顺序对我的模块子例程进行排序(我有很多子例程,如果子例程在文件中排序,我认为编辑文件会更容易)。例如给定A.pm:

package A;
use warnings;
use strict;

sub subA {
  print "A\n";
}

sub subC {
  print "C\n";
}
sub subB {
  print "B\n";
}

1;

我想运行一个sortSub A.pm 给:

package A;
use warnings;
use strict;

sub subA {
  print "A\n";
}
sub subB {
  print "B\n";
}
sub subC {
  print "C\n";
}
1;

是否有任何 CPAN 资源可以帮助完成这项任务?

【问题讨论】:

  • 如果您的某些子程序调用其他子程序,那么在某些情况下(如果您使用原型,或调用不带括号的子程序),定义它们的顺序可能会影响如何他们被解释了。
  • @tobyink 感谢您的评论。我没有使用任何原型。为什么在没有括号的情况下调用 subs 是一个问题?你能举个例子吗?
  • @HåkonHægland 如果预先声明或导入了子例程,则括号是可选的。 perl -wE 'sub foo { say "foo"; } foo' 有效,perl -wE 'foo; sub foo { say "foo"; }' 无效。
  • my $foo = bar baz(); 可以解释为my $foo = bar(baz());my $foo = "baz"->bar();(即间接对象语法),这取决于是否已经看到名为bar 的子。

标签: perl


【解决方案1】:

要解析和重新格式化 Perl 代码,您应该使用PPI

这是 Perl::CriticPerl::Tidy 用来完成所有壮举的工具。

在这种情况下,我研究了PPI::Dumper 的代码,以了解如何导航PPI 返回的文档树。

下面将解析源代码并分离出包含子例程和 cmets 的部分。它会将子程序之前的 cmets、pod 和空格与它联系起来,然后它会按名称对所有相邻的 subs 进行排序。

use strict;
use warnings;

use PPI;
use Data::Dump;

my $src = do { local $/; <DATA> };

# Load a document
my $doc = PPI::Document->new( \$src );

# Save Sub locations for later sorting
my @group = ();
my @subs  = ();

for my $i ( 0 .. $#{ $doc->{children} } ) {
    my $child = $doc->{children}[$i];

    my ( $subtype, $subname )
        = $child->isa('PPI::Statement::Sub')
        ? grep { $_->isa('PPI::Token::Word') } @{ $child->{children} }
        : ( '', '' );

    # Look for grouped subs, whitespace and comments.  Sort each group separately.
    my $is_related = ($subtype eq 'sub') || grep { $child->isa("PPI::Token::$_") } qw(Whitespace Comment Pod);

    # State change or end of stream
    if ( my $range = $is_related .. ( !$is_related || ( $i == $#{ $doc->{children} } ) ) ) {
        if ($is_related) {
            push @group, $child;

            if ( $subtype ) {
                push @subs, { name => "$subname", children => [@group] };
                @group = ();
            }
        }

        if ( $range =~ /E/ ) {
            @group = ();

            if (@subs) {
                # Sort and Flatten
                my @sorted = map { @{ $_->{children} } } sort { $a->{name} cmp $b->{name} } @subs;

                # Assign back to document, and then reset group
                my $min_index = $i - $range + 1;
                @{ $doc->{children} }[ $min_index .. $min_index + $#sorted ] = @sorted;

                @subs = ();
            }
        }
    }
}

print $doc->serialize;

1;

__DATA__
package A;
use warnings;
use strict;

=comment
Pod describing subC
=cut
sub subC {
    print "C\n";
}

INIT {
    print "Hello World";
}

sub subB {
    print "B\n";
}

# Hello subA comment
sub subA {
    print "A\n";
}

1;

输出:

package A;
use warnings;
use strict;

=comment
Pod describing subC
=cut
sub subC {
    print "C\n";
}

INIT {
    print "Hello World";
}

# Hello subA comment
sub subA {
    print "A\n";
}

sub subB {
    print "B\n";
}

1;

【讨论】:

  • 谢谢!!我认为这一定是最通用和最强大的解决方案,而且它似乎也可以完美运行!我还有一个小问题:一般来说,我们如何确定排序后的文件代表与原始文件相同的 Perl 代码? (PPI 的文档说它是“往返安全”,但这是针对您不更改树的情况..).. 有没有办法以编程方式测试这个断言? (我查看了B::Deparse,但我不确定这是否是正确的工具..)
  • PPI 是一个非常成熟的模块,所以我预计它不会有任何问题。此外,我对我的代码也非常有信心。但是,正如@tobyink 指出的那样,如果您使用原型,则 subs 的顺序可能会影响代码的解释方式。如果您从不使用那些(我没有),那么您可能没问题。为了提高安全性,您可以使用 $INPLACE_EDIT 和备份轻松实现这一点。
  • 我主要将此编码为概念证明,而不是我期望使用的东西。大多数模块不会有这样的潜艇,以至于有人无法快速复制和粘贴以进行排序。如果有这么多的子,那么很可能它可以使用架构重新设计,而不是排序。
  • @Miller;这是一个很棒的解决方案;谢谢你给我看 PPI。
【解决方案2】:

首先,这是我的解决方案;

#!/bin/sh
TOKEN=sub

gsed -e ':a;N;$!ba;s/\n/__newline__/g' "$1" > "$1.out"
gsed -i "s/__newline__\\s*$TOKEN\W/\\nsub /g" "$1.out"
sort $1.out -o $1.out
gsed -i 's/__newline__/\n/g' $1.out

用法:token_sort.sh myfile.pl

这就是它的工作原理;

  • 用占位符替换所有换行符,__newline__
  • 将所有$TOKENS(在这种情况下为潜艇)拆分到他们自己的行中
  • 使用 unix sort 对行进行排序
  • 全部换行
  • 您现在应该在 myfile.pl.out 中拥有文件的排序副本

一些注意事项;

  • 在文件顶部添加注释“#Something”或“#!/usr/bin/env perl”;这将确保标题块保持在顶部排序。
  • 已排序的块将是当前子节点到下一个子节点的开始 - 子节点上方的 cmets 将与前一个子节点一起排序。
  • 您需要使用 gnu-sed 才能工作,在 Mac 上这意味着执行“brew install gnu-sed”

【讨论】:

  • 谢谢,这是一个聪明的把戏!请注意,sub 可以出现在其他 sub 内或哈希内。在排序中不应考虑这些子程序。例如,编写my $func=sub { print "Hello\n" }; 会给出一个引用匿名子程序的变量。
  • 如果 sub 出现在字符串中会发生什么?例如my $str="This is a sub cell"; ?
  • @Håkon Hægland;简短的回答是我的解决方案不能很好地处理这些问题。 Miller 为 perl 提供了出色的解决方案。我的不太健壮,但在其他编程语言或排序块可以由通用标记分配的情况下仍然可以用作一般情况。
猜你喜欢
  • 1970-01-01
  • 2011-08-29
  • 2017-05-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-22
相关资源
最近更新 更多