1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
|
#!/usr/bin/perl
use v5.10;
use open qw(:std :utf8);
use strict;
use warnings;
use Pod::Usage;
use Getopt::Std qw(getopts);
=encoding utf8
=head1 NAME
extract_modules - determine which Perl modules a given file uses
=cut
our $VERSION = '1.104';
=head1 SYNOPSIS
Given Perl files, extract and report the Perl modules included
with C<use> or C<require>.
# print a verbose text listing
$ extract_modules filename [...]
Modules required by examples/extract_modules:
- Getopt::Std (first released with Perl 5)
- Module::CoreList (first released with Perl 5.008009)
- Pod::Usage (first released with Perl 5.006)
- strict (first released with Perl 5)
- warnings (first released with Perl 5.006)
5 module(s) in core, 0 external module(s)
# print a succint list, one module per line
$ extract_modules -l filename [...]
Getopt::Std
Module::CoreList
Pod::Usage
open
strict
warnings
# print a succinct list, modules separated by null bytes
# you might like this with xargs -0
$ extract_modules -0 filename [...]
Getopt::StdModule::CoreListPod::Usageopenstrictwarnings
# print the modules list as JSON
$ extract_modules -j filename [...]
[
"Getopt::Std",
"Module::CoreList",
"Pod::Usage",
"open",
"strict",
"warnings"
]
# print the modules list as a basic cpanfile
# https://metacpan.org/pod/cpanfile
$ extract_modules -c filename [...]
requires 'Getopt::Std', '1.23';
requires 'Module::CoreList';
requires 'Pod::Usage';
requires 'open';
requires 'strict';
requires 'warnings';
=head1 DESCRIPTION
This script does not execute the code in the files it examines. It
uses the C<Module::Extract::Use> or C<Module::ExtractUse> modules
which statically analyze the source without compiling or running it.
These modules cannot discover modules loaded dynamically through a
string eval.
=head2 Command-line options
=over 4
=item * -c cpanfile output
=item * -e exclude core modules
=item * -j JSON output
=item * -l succint list, one module per line
=item * -0 succint list, modules null separated (for xargs -0)
=cut
run(@ARGV) unless caller;
sub run {
my @args = @_;
getopts('ecjl0', \my %opts);
# if no parameters are passed, give usage information
unless( @args ) {
pod2usage( msg => 'Please supply at least one filename to analyze' );
exit;
}
use Data::Dumper;
my( $object, $method, $sub );
my @classes = qw( Module::Extract::Use Module::ExtractUse );
my %methods = (
'Module::Extract::Use' => [ 'get_modules_with_details', sub {
[ $_[0]->module, $_[0]->version ];
} ],
'Module::ExtractUse' => [ 'extract_use', sub {
say Dumper( \@_ );
[ $_[0], undef ];
} ],
);
foreach my $module ( @classes ) {
eval "require $module";
next if $@;
( $object, $method, $sub ) = ( $module->new, @{ $methods{$module} } );
}
die "No usable file scanner module found; exiting...\n" .
"Install one of these modules to make this program work:\n" .
join( "\n\t", sort keys %methods ) .
"\n"
unless defined $object;
my @Grand_modules;
foreach my $file ( @args ) {
unless ( -r $file ) {
printf STDERR "Could not read $file\n";
next;
}
my @modules = $object->$method( $file );
my $ref = ref $modules[0] ? $modules[0] : \@modules;
push @Grand_modules, map { $sub->( $_ ) } @$ref;
# remove core modules
@Grand_modules =
grep { ! defined Module::CoreList->first_release( $_->[0] ) }
@Grand_modules
if $opts{e};
next if $opts{j} || $opts{l} || $opts{0} || $opts{c}; # do these after
# Handle this here because we want the filename
long_list( $file, @Grand_modules )
}
# Handle these options after going through all the files
if( $opts{l} or $opts{0} ) { short_list( \%opts, @Grand_modules ) }
elsif( $opts{j} ) { json_list( \%opts, @Grand_modules ) }
elsif( $opts{c} ) { cpan_file( \%opts, @Grand_modules ) }
}
sub short_list {
state $Seen = {};
my $opts = shift;
my $glue = $opts->{0} ? "\000" : "\n";
print join( $glue,
grep( { ! $Seen->{$_}++ } sort map { $_->[0] } @_ ),
''
);
}
sub json_list {
state $Seen = {};
my $opts = shift;
my $glue = $opts->{0} ? "\000" : "\n";
print
"[\n\t",
join( ",\n\t",
map { qq("$_") }
grep { ! $Seen->{$_}++ }
sort
map { $_->[0] }
@_
),
"\n]\n";
}
sub cpan_file {
state $Seen = {};
my $opts = shift;
foreach my $module ( @_ ) {
printf "requires '%s'", $module->[0];
printf ", '%s'", $module->[1] if defined $module->[1];
print ";\n";
}
}
BEGIN {
my $corelist = eval { require Module::CoreList };
sub long_list {
my( $file, @modules ) = @_;
printf "Modules required by %s:\n", $file;
my( $core, $extern ) = ( 0, 0 );
foreach my $tuple ( @modules ) {
my( $module, $version ) = @$tuple;
printf " - $module%s\n",
$corelist
?
do {
my $v = Module::CoreList->first_release( $module );
$core++ if $v;
$v ? " (first released with Perl $v)" : '';
}
:
do { $extern++; '' }
}
printf "%d module(s) in core, %d external module(s)\n\n", $core, $extern;
}
}
=back
=head1 AUTHORS
Jonathan Yu C<< <frequency@cpan.org> >>
brian d foy C<< <bdfoy@cpan.org> >>
=head1 COPYRIGHT & LICENSE
Copyright © 2009-2024, brian d foy <briandfoy@pobox.com>. All rights reserved.
You can use this script under the same terms as Perl itself.
=head1 SEE ALSO
L<Module::Extract::Use>,
L<Module::ExtractUse>,
L<Module::ScanDeps>,
=cut
|