My Devel::Examine::Subs can help with some of this. It uses PPI behind the scenes. It can gather all subs in a file, or a whole directory, then list all subs in all those files. It can even examine each sub and collect only ones that have lines containing specified search patterns, print out which lines each sub starts/ends, and also how many lines are in each sub.
Collect and display all subs in all files in the current working directory:
use warnings;
use strict;
use Devel::Examine::Subs;
my $des = Devel::Examine::Subs->new(file => '.');
my $data = $des->all;
for my $file (keys %$data){
print "$file\n";
for (@{ $data->{$file} }){
print "\t$_\n";
}
}
Sample output:
lib/Test/BrewBuild/Git.pm
new
git
link
name
clone
pull
lib/Test/BrewBuild/BrewCommands.pm
new
brew
info
installed
using
available
install
remove
is_win
_legacy_perls
Get all the subs in the same manner, but collect them as objects instead to get a lot more information on each one:
use warnings;
use strict;
use Devel::Examine::Subs;
my $des = Devel::Examine::Subs->new(file => '.');
my $data = $des->all;
for my $file (keys %$data){
print "$file\n";
my $subs = $des->objects(file => $file);
for my $sub (@$subs){
print "\t" . $sub->name ."\n";
print "\t\t lines: " . $sub->line_count ."\n";
print "\t\t start: " . $sub->start ."\n";
print "\t\t end: " . $sub->end . "\n";
}
}
Sample output:
lib/Test/BrewBuild/Dispatch.pm
_fork
lines: 111
start: 146
end: 256
new
lines: 21
start: 21
end: 41
dispatch
lines: 87
start: 42
end: 128
_config
lines: 17
start: 129
end: 145
lib/Test/BrewBuild/Git.pm
name
lines: 6
start: 34
end: 39
git
lines: 17
start: 12
end: 28
The main reason I wrote this software is so that I could introspect subs accurately, and then if necessary insert code in specific subs at either a line number or search term (yes, this distribution does that as well). You can even search for specific lines in each sub, and print out the line numbers those search patterns appear on.
Of course, using the above techniques, it would be trivial to filter out which files have duplicated subs, stash all the duplicate names (along with file name) then using the objects, compare the length of the subs to do a cursory check to see if they appear to be an exact copy/paste (if the number of lines are the same). The synopsis in the docs explain how to get the objects within a hash, so that the hash's key is the sub's name. This may make things easier.
update: I forgot to mention that each subroutine object also contains the full code for the sub in $sub->code. This should help tremendously in programmatically comparing a sub from one file to the dup sub in another file. |