package Module::DependencyMap; $VERSION = 1.0; =head1 NAME Module::DependencyMap - Discover Code Dependencies =head1 DESCRIPTION This discovers the 'use' dependencies of an executable or module. Executables (pl): It creates a temp file copy of the executable and concatenates an INIT block on the end that dumps the dependencies from %INC and exits. Then depends() executes the temp file. This has the result of capturing the included (use) modules without executing the script (main). Modules (pm): 'require's the module and dumps %INC. Note: 'require'ed modules are not reported. There is no way to do that without executing the script, since that is a runtime operation and we want idempotent introspection. =head1 SYNOPSIS use Module::DependencyMap; my $depends = Module::DependencyMap::depends( 'depends.pl' ); print $depends; # or (NOT AND) # This module doesn't currently clean up after itself my $depends = Module::DependencyMap::depends( 'Data::Dumper.pm' ); print $depends; =head1 METHODS =head2 dump() Takes a hash and returns a pretty printable string. =cut sub dump { my ( %inc ) = @_; my $maps = ''; my $longest = 0; my $i = 0; for ( keys %inc ) { if ( !$i ) { $longest = length $_; } else { if ( length $_ > $longest ) { $longest = length $_; } } $i++; } for ( sort keys %inc ) { next if $_ eq __PACKAGE__ . '.pm'; my $pad = ( $longest - length $_ ) + 1; $maps .= $_ ; $maps .= " " x $pad; $maps .= "$inc{$_}\n"; } return $maps; } =head2 slurp() Takes an executable filename. Slurps the contents of a file, concatenates an INIT block onto the end of the contents, and returns the string. The INIT() block exit()s to avoid executing main when the script is executed. =cut sub slurp { my ( $script ) = @_; local $/; open( FILE, "<$script" ) || die "Couldn't open file: $script"; my $file = ; close FILE; $file .= q( INIT { use Module::DependencyMap; my $maps = Module::DependencyMap::dump( %INC ); print $maps; exit(0); } ); return $file; } =head2 depends() I asked my granddad, "Briefs or boxers?". He said, "Depends". :) Takes a script or module name. Returns a pretty printable string listing the code dependencies. =cut sub depends { my ( $script ) = @_; die "Script name must have 'pl' or 'pm' suffix" unless $script =~ /(.*)\.(pl|pm)$/; $script =~ /(.*)\.(.+?)$/; my $suffix = $2; my $results = ''; if ( $suffix eq 'pl' ) { $results = executable( $script ); } elsif ( $suffix eq 'pm' ) { $results = module( $script ); } else { die "Unrecognizable script suffix."; } return $results; } # Handle an executable script; file name suffix is: 'pl'. sub executable { my ( $script ) = @_; my $code = slurp( $script ); my $fname = "file.pl"; open( TEMP, ">$fname" ) || die "Couldn't open: $fname"; print TEMP $code; close TEMP; my $results = `$fname`; unlink $fname; return $results; } # Handle a module; file name suffix is 'pm'. sub module { my ( $script ) = @_; $script =~ s/::/\//g; $script .= '.pm' unless $script =~ /\.pm$/; eval require $script; # Note this is not robust. It assumes a blank slate for %INC. my $results = Module::DependencyMap::dump( %INC ); return $results; } 1; =head1 TODO - This should be an instantiable object to enforce the Singleton pattern. You can currently run this in a loop, resulting in %INC pollution. I may need to take a before and after snapshot to make sure I restore %INC properly. =head1 PREREQUISITES None. We don not want any %INC pollution. =head1 OSNAMES All. =head1 AUTHOR Todd Shoenfelt (aisarosenmbaum@gmail.com) =cut