File Coverage

blib/lib/Module/Installed/Tiny.pm
Criterion Covered Total %
statement 60 114 52.6
branch 34 84 40.4
condition 3 8 37.5
subroutine 7 7 100.0
pod 2 2 100.0
total 106 215 49.3


line stmt bran cond sub pod time code
1              
2             use strict;
3 2     2   197200 use warnings;
  2         17  
  2         52  
4 2     2   8  
  2         5  
  2         50  
5             use Exporter qw(import);
6 2     2   10  
  2         2  
  2         305  
7             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
8             our $DATE = '2022-07-29'; # DATE
9             our $DIST = 'Module-Installed-Tiny'; # DIST
10             our $VERSION = '0.009'; # VERSION
11              
12             our @EXPORT_OK = qw(module_installed module_source);
13              
14             our $SEPARATOR;
15             BEGIN {
16             if ($^O =~ /^(dos|os2)/i) {
17 2 50   2   20 $SEPARATOR = '\\';
    50          
18 0         0 } elsif ($^O =~ /^MacOS/i) {
19             $SEPARATOR = ':';
20 0         0 } else {
21             $SEPARATOR = '/';
22 2         1894 }
23             }
24              
25             my $name = shift;
26              
27 13     13   18 my ($name_mod, $name_pm, $name_path);
28             # name_mod is Foo::Bar form, name_pm is Foo/Bar.pm form, name_path is
29 13         18 # Foo/Bar.pm or Foo\Bar.pm (uses native path separator), name_path_prefix is
30             # Foo/Bar.
31              
32             if ($name =~ m!/|\.pm\z!) {
33             # assume it's name_pm form
34 13 100 33     71 $name_pm = $name;
    50          
35             $name_mod = $name; $name_mod =~ s/\.pm\z//; $name_mod =~ s!/!::!g;
36 1         2 $name_path = $name_pm; $name_path =~ s!/!$SEPARATOR!g if $SEPARATOR ne '/';
37 1         2 } elsif ($SEPARATOR ne '/' && $name =~ m!\Q$SEPARATOR!) {
  1         6  
  1         3  
38 1 50       2 # assume it's name_path form
  1         3  
39             $name_path = $name;
40             ($name_pm = $name_path) =~ s!\Q$SEPARATOR!/!g;
41 0         0 $name_mod = $name_pm; $name_mod =~ s/\.pm\z//; $name_mod =~ s!/!::!g;
42 0         0 } else {
43 0         0 # assume it's name_mod form
  0         0  
  0         0  
44             $name_mod = $name;
45             ($name_pm = "$name_mod.pm") =~ s!::!/!g;
46 12         18 $name_path = $name_pm; $name_path =~ s!/!$SEPARATOR!g if $SEPARATOR ne '/';
47 12         29 }
48 12 50       18  
  12         25  
49             ($name_mod, $name_pm, $name_path);
50             }
51 13         40  
52             my ($name, $opts) = @_;
53              
54             $opts //= {};
55 8     8 1 7781 $opts->{die} = 1 unless defined $opts->{die};
56              
57 8   100     25 my ($name_mod, $name_pm, $name_path) = _parse_name($name);
58 8 100       22  
59             my $index = -1;
60 8         16 for my $entry (@INC) {
61             $index++;
62 8         18 next unless defined $entry;
63 8         15 my $ref = ref($entry);
64 59         66 my ($is_hook, @hook_res);
65 59 50       88 if ($ref eq 'ARRAY') {
66 59         75 $is_hook++;
67 59         68 eval { @hook_res = $entry->[0]->($entry, $name_pm) };
68 59 50       222 if ($@) { if ($opts->{die}) { die "Can't locate $name_pm in \@INC (you may need to install the $name_mod module): $entry: $@ (\@INC contains ".join(" ", @INC).")" } else { return } }
    100          
    50          
69 0         0 } elsif (UNIVERSAL::can($entry, 'INC')) {
70 0         0 $is_hook++;
  0         0  
71 0 0       0 eval { @hook_res = $entry->INC($name_pm) };
  0 0       0  
  0         0  
  0         0  
72             if ($@) { if ($opts->{die}) { die "Can't locate $name_pm in \@INC (you may need to install the $name_mod module): $entry: $@ (\@INC contains ".join(" ", @INC).")" } else { return } }
73 1         2 } elsif ($ref eq 'CODE') {
74 1         2 $is_hook++;
  1         5  
75 1 50       44 eval { @hook_res = $entry->($entry, $name_pm) };
  1 50       3  
  0         0  
  1         3  
76             if ($@) { if ($opts->{die}) { die "Can't locate $name_pm in \@INC (you may need to install the $name_mod module): $entry: $@ (\@INC contains ".join(" ", @INC).")" } else { return } }
77 0         0 } else {
78 0         0 my $path = "$entry$SEPARATOR$name_path";
  0         0  
79 0 0       0 if (-f $path) {
  0 0       0  
  0         0  
  0         0  
80             my $fh;
81 58         102 unless (open $fh, "<", $path) {
82 58 100       703 if ($opts->{die}) { die "Can't locate $name_pm in \@INC (you may need to install the $name_mod module): $entry: $path: $! (\@INC contains ".join(" ", @INC).")" } else { return }
    100          
83 3         7 }
84 3 50       86 local $/;
85 0 0       0 return wantarray ? (scalar <$fh>, $path, $entry, $index, $name_mod, $name_pm, $name_path) : scalar <$fh>;
  0         0  
  0         0  
86             } elsif ($opts->{find_prefix}) {
87 3         15 $name_path =~ s/\.pm\z//;
88 3 100       148 if (-d $path) {
89             return wantarray ? (undef, $path, $entry, $index, $name_mod, $name_pm, $name_path) : \$path;
90 4         18 }
91 4 100       34 }
92 2 100       15 }
93              
94             if ($is_hook) {
95             next unless @hook_res;
96             my ($src, $fh, $code);
97 53 50       123 eval {
98 0 0       0 my $prepend_ref; $prepend_ref = shift @hook_res if ref($hook_res[0]) eq 'SCALAR';
99 0         0 $fh = shift @hook_res if ref($hook_res[0]) eq 'GLOB';
100 0         0 $code = shift @hook_res if ref($hook_res[0]) eq 'CODE';
101 0 0       0 my $code_state ; $code_state = shift @hook_res if @hook_res;
  0         0  
102 0 0       0 if ($fh) {
103 0 0       0 $src = "";
104 0 0       0 local $_;
  0         0  
105 0 0       0 while (!eof($fh)) {
    0          
106 0         0 $_ = <$fh>;
107 0         0 if ($code) {
108 0         0 $code->($code, $code_state);
109 0         0 }
110 0 0       0 $src .= $_;
111 0         0 }
112             $src = $$prepend_ref . $src if $prepend_ref;
113 0         0 } elsif ($code) {
114             $src = "";
115 0 0       0 local $_;
116             while ($code->($code, $code_state)) {
117 0         0 $src .= $_;
118 0         0 }
119 0         0 $src = $$prepend_ref . $src if $prepend_ref;
120 0         0 }
121             }; # eval
122 0 0       0 if ($@) { if ($opts->{die}) { die "Can't locate $name_pm in \@INC (you may need to install the $name_mod module): $entry: ".($fh || $code).": $@ (\@INC contains ".join(" ", @INC).")" } else { return } }
123             return wantarray ? ($src, undef, $entry, $index, $name_mod, $name_pm, $name_path) : $src;
124             } # if $is_hook
125 0 0 0     0 }
  0 0       0  
  0         0  
  0         0  
126 0 0       0  
127             if ($opts->{die}) {
128             die "Can't locate $name_pm in \@INC (you may need to install the $name_mod module) (\@INC contains ".join(" ", @INC).")";
129             } else {
130 2 50       5 return;
131 0         0 }
132             }
133 2         7  
134             my ($name, $opts) = @_;
135              
136             # convert Foo::Bar -> Foo/Bar.pm
137             my ($name_mod, $name_pm, $name_path) = _parse_name($name);
138 5     5 1 3029  
139             return 1 if exists $INC{$name_pm};
140              
141 5         12 my $res = module_source($name, {%{ $opts || {}}, die=>0});
142             defined($res) ? 1:0;
143 5 100       19 }
144              
145 3 50       17 1;
  3         21  
146 3 100       24 # ABSTRACT: Check if a module is installed, with as little code as possible
147              
148              
149             =pod
150              
151             =encoding UTF-8
152              
153             =head1 NAME
154              
155             Module::Installed::Tiny - Check if a module is installed, with as little code as possible
156              
157             =head1 VERSION
158              
159             This document describes version 0.009 of Module::Installed::Tiny (from Perl distribution Module-Installed-Tiny), released on 2022-07-29.
160              
161             =head1 SYNOPSIS
162              
163             use Module::Installed::Tiny qw(module_installed module_source);
164              
165             # check if a module is available
166             if (module_installed "Foo::Bar") {
167             # Foo::Bar is available
168             } elsif (module_installed "Foo/Baz.pm") {
169             # Foo::Baz is available
170             }
171              
172             # get a module's source code, dies on failure
173             my $src = module_source("Foo/Baz.pm");
174              
175             =head1 DESCRIPTION
176              
177             To check if a module is installed (available), generally the simplest way is to
178             try to C<require()> it:
179              
180             if (eval { require Foo::Bar; 1 }) {
181             # Foo::Bar is available
182             }
183             # or
184             my $mod_pm = "Foo/Bar.pm";
185             if (eval { require $mod_pm; 1 }) {
186             # Foo::Bar is available
187             }
188              
189             However, this actually loads the module. There are some cases where this is not
190             desirable: 1) we have to check a lot of modules (actually loading the modules
191             will take a lot of CPU time and memory; 2) some of the modules conflict with one
192             another and cannot all be loaded; 3) the module is OS specific and might not
193             load under another OS; 4) we simply do not want to execute the module, for
194             security or other reasons.
195              
196             C<Module::Installed::Tiny> provides a routine C<module_installed()> which works
197             like Perl's C<require> but does not actually load the module.
198              
199             This module does not require any other module except L<Exporter>.
200              
201             =head1 FUNCTIONS
202              
203             =head2 module_source
204              
205             Usage:
206              
207             module_source($name [ , \%opts ]) => str | list
208              
209             Return module's source code, without actually loading/executing it. Module
210             source will be searched in C<@INC> the way Perl's C<require()> finds modules.
211             This include executing require hooks in C<@INC> if there are any.
212              
213             Die on failure (e.g. module named C<$name> not found in C<@INC> or module source
214             file cannot be read) with the same/similar message as Perl's C<require()>:
215              
216             Can't locate Foo/Bar.pm (you may need to install the Foo::Bar module) ...
217              
218             Module C<$name> can be in the form of C<Foo::Bar>, C<Foo/Bar.pm> or
219             C<Foo\Bar.pm> (on Windows).
220              
221             In list context:
222              
223             # [0] [1] [2] [3] [4] [5] [6]
224             my ($src, $path, $entry, $index, $name_mod, $name_pm, $name_path) = module_source($name);
225              
226             where:
227              
228             =over
229              
230             =item * $src
231              
232             String. The module source code.
233              
234             =item * $path
235              
236             String. The filesystem path (C<undef> if source comes from a require hook).
237              
238             =item * $entry
239              
240             The element in C<@INC> where the source comes from.
241              
242             =item * $index
243              
244             Integer, the index of entry in C<@INC> where the source comes from, 0 means the
245             first entry.
246              
247             =item * $name_mod
248              
249             Module name normalized to C<Foo::Bar> form.
250              
251             =item * $name_pm
252              
253             Module name normalized to C<Foo/Bar.pm> form.
254              
255             =item * $name_path
256              
257             Module name normalized to C<Foo/Bar.pm> form or C<Foo\Bar.pm> form depending on
258             the native path separator character.
259              
260             =back
261              
262             Options:
263              
264             =over
265              
266             =item * die
267              
268             Bool. Default true. If set to false, won't die upon failure but instead will
269             return undef (or empty list in list context).
270              
271             =item * find_prefix
272              
273             Bool. If set to true, when a module (e.g. C<Foo/Bar.pm>) is not found in the
274             fileysstem but its directory is (C<Foo/Bar/>), then instead of dying or
275             returning undef/empty list, the function will return:
276              
277             \$path
278              
279             in scalar context, or:
280              
281             (undef, $path, $entry, $index)
282              
283             in list context. In scalar context, you can differentiate path from module
284             source because the path is returned as a scalar reference. So to get the path:
285              
286             $source_or_pathref = module_source("Foo/Bar.pm", {find_prefix=>1});
287             if (ref $source_or_pathref eq 'SCALAR') {
288             say "Path is ", $$source_or_pathref;
289             } else {
290             say "Module source code is $source_or_pathref";
291             }
292              
293             =back
294              
295             =head2 module_installed
296              
297             Usage:
298              
299             module_installed($name [ , \%opts ]) => bool
300              
301             Check that module named C<$name> is available to load, without actually
302             loading/executing the module. Module will be searched in C<@INC> the way Perl's
303             C<require()> finds modules. This include executing require hooks in C<@INC> if
304             there are any.
305              
306             Note that this does not guarantee that the module can eventually be loaded
307             successfully, as there might be syntax or runtime errors in the module's source.
308             To check for that, one would need to actually load the module using C<require>.
309              
310             Module C<$name> can be in the form of C<Foo::Bar>, C<Foo/Bar.pm> or
311             F<Foo\Bar.pm> (on Windows).
312              
313             Options:
314              
315             =over
316              
317             =item * find_prefix
318              
319             See L</module_source> documentation.
320              
321             =back
322              
323             =head1 FAQ
324              
325             =head2 How to get module source without dying? I want to just get undef if module source is not available.
326              
327             Set the C<die> option to false:
328              
329             my $src = module_source($name, {die=>0});
330              
331             This is what C<module_installed()> does.
332              
333             =head2 How to know which @INC entry the source comes from?
334              
335             Call the L</module_source> in list context, where you will get more information
336             including the entry. See the function documentation for more details.
337              
338             =head1 HOMEPAGE
339              
340             Please visit the project's homepage at L<https://metacpan.org/release/Module-Installed-Tiny>.
341              
342             =head1 SOURCE
343              
344             Source repository is at L<https://github.com/perlancar/perl-Module-Installed-Tiny>.
345              
346             =head1 SEE ALSO
347              
348             L<Module::Load::Conditional> provides C<check_install> which also does what
349             C<module_installed> does, plus can check module version. It also has a couple
350             other knobs to customize its behavior. It's less tiny than
351             Module::Installed::Tiny though.
352              
353             L<Module::Path> and L<Module::Path::More>. These modules can also be used to
354             check if a module on the filesystem is available. They do not handle require
355             hooks, nor do they actually check that the module file is readable.
356              
357             =head1 AUTHOR
358              
359             perlancar <perlancar@cpan.org>
360              
361             =head1 CONTRIBUTING
362              
363              
364             To contribute, you can send patches by email/via RT, or send pull requests on
365             GitHub.
366              
367             Most of the time, you don't need to build the distribution yourself. You can
368             simply modify the code, then test via:
369              
370             % prove -l
371              
372             If you want to build the distribution (e.g. to try to install it locally on your
373             system), you can install L<Dist::Zilla>,
374             L<Dist::Zilla::PluginBundle::Author::PERLANCAR>, and sometimes one or two other
375             Dist::Zilla plugin and/or Pod::Weaver::Plugin. Any additional steps required
376             beyond that are considered a bug and can be reported to me.
377              
378             =head1 COPYRIGHT AND LICENSE
379              
380             This software is copyright (c) 2022, 2021, 2020, 2016 by perlancar <perlancar@cpan.org>.
381              
382             This is free software; you can redistribute it and/or modify it under
383             the same terms as the Perl 5 programming language system itself.
384              
385             =head1 BUGS
386              
387             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Module-Installed-Tiny>
388              
389             When submitting a bug or request, please include a test-file or a
390             patch to an existing test-file that illustrates the bug or desired
391             feature.
392              
393             =cut