This program collects the functions by module in a directory full of modules and checks a code base against it. This tells you which functions you are actually using. Which isn't quite what I wanted, but close.
I can see some additions I could want. Setting the library directory and code directories via
Getopt::Long
would be the first one. And it doesn't quite tell me what I want to know in all cases. If I use a function in a program that never gets called, it still gives me a result. But this is a place to start.And because of this, I now know that, within the stack of previously-invented wheels called CPAN, there's a module called Regexp::Common that holds a stack of established regular expressions. I wanted to pull out all comments out of my programs for testing, so that a commented-out function doesn't count.
- #!/usr/bin/perl
- use 5.010 ;
- use strict ;
- use warnings ;
- use Cwd 'abs_path' ;
- use Regexp::Common qw /comment/ ;
- use subs qw{
- check_programs
- module_list
- decomment
- drop_pm
- get_module_subs
- pull_package_name
- pull_module_name
- pull_sub_name
- } ;
- my $modules = module_list '/path/to/my/lib' ;
- my $directories = [
- '/code/directory/one',
- '/code/directory/two',
- '/code/directory/three', ] ;
- my $data = check_programs( $directories, $modules ) ;
- for my $mod ( sort keys %$data ) {
- my $module = $data->{ $mod } ;
- say $mod ;
- for my $sub ( sort keys %$module ) {
- my $subroutine = $module->{ $sub } ;
- say join "\t", '',
- ( $subroutine->{ count } ? $subroutine->{ count } : 0 ) ,
- $sub,
- ;
- }
- }
- exit ;
- ########## ######### ######### ######### ######### #########
- ########## ######### Subroutines ######### #########
- ########## ######### ######### ######### ######### #########
- #--------- --------- --------- --------- --------- --------- ---------
- # The core of the program
- sub check_programs {
- my ( $directories, $modules ) = @_ ;
- my $data ;
- for my $program_dir ( @$directories ) {
- my $program_directory = abs_path $program_dir ;
- chdir $program_directory ;
- #say $program_directory ;
- my @directory = glob '*.cgi *.pl *.pm' ;
- my $programs ;
- @$programs = map {
- { $_ => get_program( $_ ) }
- } @directory ;
- for my $program ( @$programs ) {
- my $k ;
- ( $k ) = keys %$program ;
- my $v = $program->{ $k } ;
- #say join "\t", '', $k ;
- for my $module ( @$modules ) {
- my $mk ;
- ( $mk ) = keys %$module ;
- my $mv = $module->{ $mk } ;
- #say join "\t", '', '', $mk ;
- for my $sub ( @$mv ) {
- my $result = $v =~ /$sub/ ? 1 : 0 ;
- #$result
- # and say join "\t", '', '', '', $result, $sub ;
- $data->{ $mk }->{ $sub }->{ exists } = 1 ;
- if ( $result ) {
- $data->{ $mk }->{ $sub }->{ count }++ ;
- push @{ $data->{ $mk }->{ $sub }->{ used } },
- abs_path $k ;
- }
- }
- }
- }
- }
- return $data ;
- }
- #--------- --------- --------- --------- --------- --------- ---------
- # returns the contents of a filename, if it contains 'perl' in the top
- sub get_program {
- my ( $filename ) = @_ ;
- if ( -f $filename ) {
- if ( open my $fh, '<', $filename ) {
- my @lines =
- map { decomment $_ } <$fh> ;
- #return 0 if $lines[0] !~ m/perl/ ;
- return join '', @lines ;
- close $fh ;
- }
- }
- return 0 ;
- }
- #--------- --------- --------- --------- --------- --------- ---------
- # removes Perl comments
- sub decomment {
- my ( $code ) = @_ ;
- #chomp $code ;
- $code =~ s/$RE{comment}{Perl}// ;
- return $code ;
- }
- #--------- --------- --------- --------- --------- --------- ---------
- # returns an array ref of module names with an array of subroutines
- # the module contains
- sub module_list {
- my ( $dir ) = @_ ;
- my $directory = abs_path $dir ;
- chdir $directory ;
- my $output ;
- my @directory = glob '*.pm' ;
- @$output = map {
- {
- ( pull_module_name join '/', $directory, $_ . '.pm' ) =>
- ( get_module_subs join '/', $directory, $_ . '.pm' )
- }
- }
- map { drop_pm $_ } @directory ;
- return $output ;
- }
- #--------- --------- --------- --------- --------- --------- ---------
- # returns an array ref to all the functions (minus internal functions
- # whose name starts with _) within a given module
- sub get_module_subs {
- my ( $mod_path ) = @_ ;
- my @output ;
- if ( -f $mod_path ) {
- if ( open my $fh, '<', $mod_path ) {
- my @lines = <$fh> ;
- push @output, grep { !/^_/ }
- map { pull_sub_name $_ }
- grep { /^\s*sub / } @lines ;
- close $fh ;
- }
- }
- @output = sort @output ;
- return \@output ;
- }
- #--------- --------- --------- --------- --------- --------- ---------
- # return the package name of a module
- sub pull_module_name {
- my ( $mod_path ) = @_ ;
- my @output ;
- if ( -f $mod_path ) {
- if ( open my $fh, '<', $mod_path ) {
- my @lines = <$fh> ;
- push @output, map { pull_package_name $_ }
- grep { /^\s*package / } @lines ;
- close $fh ;
- }
- }
- return $output[ 0 ] ;
- }
- #--------- --------- --------- --------- --------- --------- ---------
- # return the package name of a 'package Package::Name ;' string
- sub pull_package_name {
- my ( $in ) = @_ ;
- chomp $in ;
- $in = ( split m{\s*package\s*}, $in )[ 1 ] ;
- $in = ( split m/\s*;\s*/, $in )[ 0 ] ;
- return $in ;
- }
- #--------- --------- --------- --------- --------- --------- ---------
- # return only the subroutine name from a 'sub sub_name { ' string
- sub pull_sub_name {
- my ( $in ) = @_ ;
- chomp $in ;
- $in = ( split m{\s*sub\s*}, $in )[ 1 ] ;
- $in = ( split m/\s*{\s*/, $in )[ 0 ] ;
- return $in ;
- }
- #--------- --------- --------- --------- --------- --------- ---------
- # remove '.pm' from end of module file names
- sub drop_pm {
- my ( $in ) = @_ ;
- $in =~ s/\.pm$// ;
- return $in ;
- }
I'll confess that I wish I wrote it more Higher Order, but that can be changed later.
ReplyDelete