File Coverage

blib/lib/Module/Loadable.pm
Criterion Covered Total %
statement 45 73 61.6
branch 18 46 39.1
condition n/a
subroutine 8 8 100.0
pod 2 2 100.0
total 73 129 56.5


line stmt bran cond sub pod time code
1             package Module::Loadable;
2              
3             our $DATE = '2016-08-02'; # DATE
4             our $VERSION = '0.001'; # VERSION
5              
6 2     2   779 use strict;
  2         2  
  2         42  
7 2     2   6 use warnings;
  2         1  
  2         42  
8              
9 2     2   6 use Scalar::Util qw(blessed);
  2         2  
  2         156  
10              
11 2     2   9 use Exporter qw(import);
  2         2  
  2         224  
12             our @EXPORT_OK = qw(module_loadable module_source);
13              
14             our $SEPARATOR;
15             BEGIN {
16 2 50   2   14 if ($^O =~ /^(dos|os2)/i) {
    50          
17 0         0 $SEPARATOR = '\\';
18             } elsif ($^O =~ /^MacOS/i) {
19 0         0 $SEPARATOR = ':';
20             } else {
21 2         911 $SEPARATOR = '/';
22             }
23             }
24              
25             sub _module_source {
26 4     4   5 my $name_pm = shift;
27              
28 4         9 for my $entry (@INC) {
29 32 50       42 next unless defined $entry;
30 32         22 my $ref = ref($entry);
31 32         23 my ($is_hook, @hook_res);
32 32 50       76 if ($ref eq 'ARRAY') {
    50          
    100          
33 0         0 $is_hook++;
34 0         0 @hook_res = $entry->[0]->($entry, $name_pm);
35             } elsif (blessed $entry) {
36 0         0 $is_hook++;
37 0         0 @hook_res = $entry->INC($name_pm);
38             } elsif ($ref eq 'CODE') {
39 1         2 $is_hook++;
40 1         3 @hook_res = $entry->($entry, $name_pm);
41             } else {
42 31         33 my $path = "$entry$SEPARATOR$name_pm";
43 31 100       392 if (-f $path) {
44 2 50       56 open my($fh), "<", $path
45             or die "Can't locate $name_pm: $path: $!";
46 2         5 local $/;
47 2         47 return scalar <$fh>;
48             }
49             }
50              
51 29 50       41 if ($is_hook) {
52 0 0       0 next unless @hook_res;
53 0 0       0 my $prepend_ref = shift @hook_res if ref($hook_res[0]) eq 'SCALAR';
54 0 0       0 my $fh = shift @hook_res if ref($hook_res[0]) eq 'GLOB';
55 0 0       0 my $code = shift @hook_res if ref($hook_res[0]) eq 'CODE';
56 0 0       0 my $code_state = shift @hook_res if @hook_res;
57 0 0       0 if ($fh) {
    0          
58 0         0 my $src = "";
59 0         0 local $_;
60 0         0 while (!eof($fh)) {
61 0         0 $_ = <$fh>;
62 0 0       0 if ($code) {
63 0         0 $code->($code, $code_state);
64             }
65 0         0 $src .= $_;
66             }
67 0 0       0 $src = $$prepend_ref . $src if $prepend_ref;
68 0         0 return $src;
69             } elsif ($code) {
70 0         0 my $src = "";
71 0         0 local $_;
72 0         0 while ($code->($code, $code_state)) {
73 0         0 $src .= $_;
74             }
75 0 0       0 $src = $$prepend_ref . $src if $prepend_ref;
76 0         0 return $src;
77             }
78             }
79             }
80              
81 1         15 die "Can't locate $name_pm in \@INC (\@INC contains: ".join(" ", @INC).")";
82             }
83              
84             sub module_source {
85 1     1 1 808 my $name = shift;
86              
87             # convert Foo::Bar -> Foo/Bar.pm
88 1         1 my $name_pm;
89 1 50       6 if ($name =~ /\A\w+(?:::\w+)*\z/) {
90 1         3 ($name_pm = "$name.pm") =~ s!::!$SEPARATOR!g;
91             } else {
92 0         0 $name_pm = $name;
93             }
94              
95 1         2 _module_source $name_pm;
96             }
97              
98             sub module_loadable {
99 5     5 1 1424 my $name = shift;
100              
101             # convert Foo::Bar -> Foo/Bar.pm
102 5         6 my $name_pm;
103 5 100       24 if ($name =~ /\A\w+(?:::\w+)*\z/) {
104 4         13 ($name_pm = "$name.pm") =~ s!::!$SEPARATOR!g;
105             } else {
106 1         2 $name_pm = $name;
107             }
108              
109 5 100       19 return 1 if exists $INC{$name_pm};
110              
111 3 100       4 if (eval { _module_source $name_pm; 1 }) {
  3         8  
  1         4  
112 1         4 1;
113             } else {
114 2         40 0;
115             }
116             }
117              
118             1;
119             # ABSTRACT: Check if a module is loadable without actually loading it
120              
121             __END__
122              
123             =pod
124              
125             =encoding UTF-8
126              
127             =head1 NAME
128              
129             Module::Loadable - Check if a module is loadable without actually loading it
130              
131             =head1 VERSION
132              
133             This document describes version 0.001 of Module::Loadable (from Perl distribution Module-Loadable), released on 2016-08-02.
134              
135             =head1 SYNOPSIS
136              
137             use Module::Loadable qw(module_loadable module_source);
138              
139             # check if a module is available
140             if (module_loadable "Foo::Bar") {
141             # Foo::Bar is available
142             } elsif (module_loadable "Foo/Baz.pm") {
143             # Foo::Baz is available
144             }
145              
146             # get a module's source code, dies on failure
147             my $src = module_source("Foo/Baz.pm");
148              
149             =head1 DESCRIPTION
150              
151             To check if a module is loadable (available), generally the simplest way is to
152             try to C<require()> it:
153              
154             if (eval { require Foo::Bar; 1 }) {
155             # Foo::Bar is available
156             }
157              
158             However, this actually loads the module. If a large number of modules need to be
159             checked, this can potentially consume a lot of CPU time and memory.
160              
161             C<Module::Loadable> provides a routine C<module_loadable()> which works like
162             Perl's C<require> but does not actually load the module.
163              
164             =head1 FUNCTIONS
165              
166             =head2 module_loadable($name) => bool
167              
168             Check that module named C<$name> is loadable, without actually loading it.
169             C<$name> will be converted from C<Foo::Bar> format to C<Foo/Bar.pm>.
170              
171             It works by following the behavior of Perl's C<require>, except the actual
172             loading/executing part. First, it checks if C<$name> is already in C<%INC>,
173             returning true immediately if that is the case. Then it will iterate each entry
174             in C<@INC>. If the entry is a coderef or object or arrayref,
175             C<module_loadable()> will treat it like a hook and call it like Perl's
176             C<require()> does as described in L<perlfunc>. Otherwise, the entry will be
177             treated like a directory name and the module's file will be searched on the
178             filesystem.
179              
180             =head2 module_source($name) => str
181              
182             Return module's source code, without actually loading it. Die on failure.
183              
184             =head1 HOMEPAGE
185              
186             Please visit the project's homepage at L<https://metacpan.org/release/Module-Loadable>.
187              
188             =head1 SOURCE
189              
190             Source repository is at L<https://github.com/perlancar/perl-Module-Loadable>.
191              
192             =head1 BUGS
193              
194             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Module-Loadable>
195              
196             When submitting a bug or request, please include a test-file or a
197             patch to an existing test-file that illustrates the bug or desired
198             feature.
199              
200             =head1 SEE ALSO
201              
202             L<Module::Path> and L<Module::Path::More>. These modules can also be used to
203             check if a module on the filesystem is available. It iterates directories in
204             C<@INC> to try to find the module's file, but will not work with fatpacked (see
205             L<App::FatPacker> or L<Module::FatPack>) or datapacked (see L<Module::DataPack>)
206             scripts or generally when there is a hook in C<@INC>. C<Module::Loadable>, on
207             the other hand, handles require hook like Perl's C<require()>.
208              
209             Also, those two modules at the time of this writing currently does not actually
210             read the module file. In the case of, say, permission problem, those two will
211             still return the path but the module might not actually readable.
212              
213             =head1 AUTHOR
214              
215             perlancar <perlancar@cpan.org>
216              
217             =head1 COPYRIGHT AND LICENSE
218              
219             This software is copyright (c) 2016 by perlancar@cpan.org.
220              
221             This is free software; you can redistribute it and/or modify it under
222             the same terms as the Perl 5 programming language system itself.
223              
224             =cut