File Coverage

blib/lib/Module/Lazy.pm
Criterion Covered Total %
statement 67 67 100.0
branch 18 26 69.2
condition 1 3 33.3
subroutine 14 14 100.0
pod n/a
total 100 110 90.9


line stmt bran cond sub pod time code
1             package Module::Lazy;
2              
3 11     11   506156 use 5.008;
  11         113  
4 11     11   58 use strict;
  11         22  
  11         264  
5 11     11   55 use warnings;
  11         19  
  11         547  
6             our $VERSION = '0.03';
7              
8             =head1 NAME
9              
10             Module::Lazy - postpone loading a module until it's actually used
11              
12             =head1 SYNOPSIS
13              
14             use Module::Lazy "My::Module";
15             # My::Module has not been loaded
16              
17             my $var = My::Module->new;
18             # My::Module is loaded now, and new() method is called
19              
20             no Module::Lazy;
21             # Force loading of all postponed modules
22              
23             =head1 DESCRIPTION
24              
25             In large projects loading all the dependencies may take a lot of time.
26             This module attempts to reduce the startup time by postponing initialization.
27             The improvement be significant for unit test scripts
28             and small command-line tools
29             which do not utilize all the functionality at once.
30              
31             This comes at a cost of reduced stability,
32             as load-time errors are also postponed.
33             The C directive is provided to mitigate the risk
34             by forcing the pending modules to load.
35              
36             =head1 EXPORTED FUNCTIONS
37              
38             None.
39              
40             =head1 METHODS
41              
42             =cut
43              
44 11     11   75 use Carp;
  11         37  
  11         8897  
45              
46             =head2 import
47              
48             When C is called,
49             the module in question is not loaded.
50             A stub package with the same name is created instead.
51              
52             Should any method call be performed on the stub package,
53             it loads the original one and jumps to respective method.
54              
55             In particular, C and C are overloaded
56             and will trigger module loading.
57              
58             Upon loading, C is not called on the target package.
59             This MAY change in the future.
60              
61             No extra options (except from target module name) are allowed.
62              
63             =cut
64              
65             my $dont;
66             my %seen;
67             my $inc_stub = "pending load by ".__PACKAGE__;
68              
69             sub import {
70 14     14   1469 my ($class, $target, @rest) = @_;
71              
72             # bare use statement is ok
73 14 100       63 return unless defined $target;
74              
75 13 100       66 croak "Usage: use Module::Lazy 'Module::Name'; extra options not supported"
76             unless @rest == 0;
77              
78             # return ASAP if already loaded by us or Perl itself
79 12 100       44 return if $seen{$target};
80 11         19 my $mod = $target;
81 11         70 $mod =~ s,::,/,g;
82 11         24 $mod .= ".pm";
83              
84 11 50       38 return if $INC{$mod};
85 11 50       44 return _load( $target, $mod )
86             if $dont;
87              
88 11 50       115 croak "Bad module name '$target'"
89             unless $target =~ /^[A-Za-z_][A-Za-z_0-9]*(?:::[A-Za-z_0-9]+)*$/;
90              
91 11         37 $seen{$target} = $mod;
92              
93             # If $target is later require'd directly,
94             # autoload and destroy will be overwritten and will cause a warning.
95             # Preventing them from being loaded seems like a lesser evil.
96 11         24 $INC{$mod} = $inc_stub;
97              
98             _set_function( $target, AUTOLOAD => sub {
99 4     4   1578 our $AUTOLOAD;
100 4         29 $AUTOLOAD =~ s/.*:://;
101 4         15 my $jump = _jump( $target, $AUTOLOAD );
102 4         17 goto $jump;
103 11         66 } );
104              
105             # Provide DESTROY just in case someone blesses an object directly
106             # without ever loading a module
107 11         34 _set_function( $target, DESTROY => _jump( $target, DESTROY => "no_die" ) );
108              
109 11         31 foreach (qw( can isa )) {
110 22         48 _set_function( $target, $_ => _jump( $target, $_ ) );
111             };
112             };
113              
114             =head2 unimport
115              
116             Calling C or, alternatively, Cunimport;>
117             will cause all postponed modules to be loaded immediately,
118             in alphabetical order.
119              
120             This may be useful to avoid deferred errors and/or side effects
121             of module loading.
122              
123             No extra options to unimport are supported.
124              
125             =cut
126              
127             sub unimport {
128 1     1   619 my $class = shift;
129              
130 1 50       6 croak "usage: no Module::Lazy;"
131             if @_;
132              
133 1         2 $dont++;
134             # sort keys to ensure load order stability in case of bugs
135 1         5 foreach (sort keys %seen) {
136 1         3 _inflate($_);
137             };
138             };
139              
140             my %known_method;
141             sub _inflate {
142 11     11   23 my $target = shift;
143              
144             # TODO distinguish between "not seen" and "already loaded"
145 11         34 my $mod = delete $seen{$target};
146 11 50       59 croak "Module '$target' was never loaded via Module::Lazy, that's possibly a bug"
147             unless $mod;
148              
149             croak "Module '$target' already loaded from '$INC{$mod}'"
150 11 50 33     86 unless $INC{$mod} and $INC{$mod} eq $inc_stub;
151              
152             # reset stub methods prior to loading
153 11 50       22 foreach (keys %{ $known_method{$target} || {} }) {
  11         65  
154 44         90 _set_function( $target, $_ => undef );
155             };
156              
157             # make the module loadable again
158 11         33 delete $INC{$mod};
159 11         34 _load( $target, $mod );
160             };
161              
162             sub _load {
163 11     11   27 my ($target, $mod) = @_;
164              
165             package
166             Module::Lazy::_::quarantine;
167              
168 11         32 local $Carp::Internal{ __PACKAGE__ } = 1;
169 11         4136 require $mod;
170             # TODO maybe $target->import()
171             };
172              
173             sub _jump {
174 37     37   83 my ($target, $todo, $nodie) = @_;
175              
176             return sub {
177 10     10   1097 _inflate( $target );
178              
179 10         1340 my $jump = $target->can($todo);
180 10 100       77 goto $jump
181             if $jump; # TODO should also check it's a CODEREF
182              
183 1 50       7 croak qq{Can't locate object method "$todo" via package "$target"}
184             unless $nodie;
185 37         160 };
186             };
187              
188             sub _set_function {
189 88     88   170 my ($target, $name, $code) = @_;
190              
191 88 100       200 if (ref $code) {
192 44         92 $known_method{$target}{$name}++;
193 11     11   89 no strict 'refs'; ## no critic
  11         22  
  11         561  
194 44         60 *{ $target."::".$name } = $code;
  44         6156  
195             } else {
196 11     11   69 no strict 'refs'; ## no critic
  11         28  
  11         1070  
197 44         58 delete ${ $target."::" }{ $name };
  44         210  
198             };
199             };
200              
201             =head1 AUTHOR
202              
203             Konstantin S. Uvarin, C<< >>
204              
205             =head1 BUGS
206              
207             =over
208              
209             =item * C does not work with lazy-loaded parent classes.
210              
211             =item * C is not called on the modules being loaded.
212             The decision is yet to be made whether it's good or bad.
213              
214             =item * no way to preload prototyped exported functions
215             (that's what L does),
216             but maybe there should be?
217              
218             =item * certainly not enough interoperability tests.
219              
220             =back
221              
222             Please report bugs via github or RT:
223              
224             =over
225              
226             =item * L
227              
228             =item * C
229              
230             =item * L
231              
232             =back
233              
234             =head1 SUPPORT
235              
236             You can find documentation for this module with the C command.
237              
238             perldoc Module::Lazy
239              
240             You can also look for information at:
241              
242             =over 4
243              
244             =item * github: L
245              
246             =item * RT: CPAN's request tracker (report bugs here)
247              
248             L
249              
250             =item * AnnoCPAN: Annotated CPAN documentation
251              
252             L
253              
254             =item * CPAN Ratings
255              
256             L
257              
258             =item * Search CPAN
259              
260             L
261              
262             =back
263              
264             =head1 SEE ALSO
265              
266             L is another module with similar idea, however,
267             it does it for imported functions rather than methods.
268              
269             =head1 ACKNOWLEDGEMENTS
270              
271             =head1 LICENSE AND COPYRIGHT
272              
273             Copyright 2019 Konstantin S. Uvarin.
274              
275             This program is free software; you can redistribute it and/or modify it
276             under the terms of the the Artistic License (2.0). You may obtain a
277             copy of the full license at:
278              
279             L
280              
281             Any use, modification, and distribution of the Standard or Modified
282             Versions is governed by this Artistic License. By using, modifying or
283             distributing the Package, you accept this license. Do not use, modify,
284             or distribute the Package, if you do not accept this license.
285              
286             If your Modified Version has been derived from a Modified Version made
287             by someone other than you, you are nevertheless required to ensure that
288             your Modified Version complies with the requirements of this license.
289              
290             This license does not grant you the right to use any trademark, service
291             mark, tradename, or logo of the Copyright Holder.
292              
293             This license includes the non-exclusive, worldwide, free-of-charge
294             patent license to make, have made, use, offer to sell, sell, import and
295             otherwise transfer the Package with respect to any patent claims
296             licensable by the Copyright Holder that are necessarily infringed by the
297             Package. If you institute patent litigation (including a cross-claim or
298             counterclaim) against any party alleging that the Package constitutes
299             direct or contributory patent infringement, then this Artistic License
300             to you shall terminate on the date that such litigation is filed.
301              
302             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
303             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
304             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
305             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
306             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
307             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
308             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
309             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
310              
311             =cut
312              
313             1; # End of Module::Lazy